Initial provider implementation (#18)
This brings over the initial buildx prototype from pulumi/pulumi-docker and fixes various build and release issues.
This commit is contained in:
31
provider/internal/auth.go
Normal file
31
provider/internal/auth.go
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright 2024, 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 internal
|
||||
|
||||
import "github.com/pulumi/pulumi-go-provider/infer"
|
||||
|
||||
// Registry contains credentials for authenticating with a remote registry.
|
||||
type Registry struct {
|
||||
Address string `pulumi:"address"`
|
||||
Password string `pulumi:"password,optional" provider:"secret"`
|
||||
Username string `pulumi:"username,optional"`
|
||||
}
|
||||
|
||||
// Annotate sets docstrings on Registry.
|
||||
func (r *Registry) Annotate(a infer.Annotator) {
|
||||
a.Describe(&r.Address, `The registry's address (e.g. "docker.io").`)
|
||||
a.Describe(&r.Username, `Username for the registry.`)
|
||||
a.Describe(&r.Password, `Password or token for the registry.`)
|
||||
}
|
||||
38
provider/internal/builder.go
Normal file
38
provider/internal/builder.go
Normal file
@@ -0,0 +1,38 @@
|
||||
// Copyright 2024, 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 internal
|
||||
|
||||
import (
|
||||
"github.com/pulumi/pulumi-go-provider/infer"
|
||||
)
|
||||
|
||||
var _ infer.Annotated = (*BuilderConfig)(nil)
|
||||
|
||||
// BuilderConfig configures the builder to use for an image build.
|
||||
type BuilderConfig struct {
|
||||
Name string `pulumi:"name,optional"`
|
||||
}
|
||||
|
||||
// Annotate sets docstrings on BuilderConfig.
|
||||
func (b *BuilderConfig) Annotate(a infer.Annotator) {
|
||||
a.Describe(&b.Name, dedent(`
|
||||
Name of an existing buildx builder to use.
|
||||
|
||||
Only "docker-container", "kubernetes", or "remote" drivers are
|
||||
supported. The legacy "docker" driver is not supported.
|
||||
|
||||
Equivalent to Docker's "--builder" flag.
|
||||
`))
|
||||
}
|
||||
746
provider/internal/cache.go
Normal file
746
provider/internal/cache.go
Normal file
@@ -0,0 +1,746 @@
|
||||
// Copyright 2024, 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 internal
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
controllerapi "github.com/docker/buildx/controller/pb"
|
||||
"github.com/docker/buildx/util/buildflags"
|
||||
|
||||
"github.com/pulumi/pulumi-go-provider/infer"
|
||||
)
|
||||
|
||||
var (
|
||||
_ fmt.Stringer = (*CacheFrom)(nil)
|
||||
_ fmt.Stringer = (*CacheFromAzureBlob)(nil)
|
||||
_ fmt.Stringer = (*CacheFromGitHubActions)(nil)
|
||||
_ fmt.Stringer = (*CacheFromLocal)(nil)
|
||||
_ fmt.Stringer = (*CacheFromRegistry)(nil)
|
||||
_ fmt.Stringer = (*CacheFromS3)(nil)
|
||||
_ fmt.Stringer = (*CacheTo)(nil)
|
||||
_ fmt.Stringer = (*CacheToAzureBlob)(nil)
|
||||
_ fmt.Stringer = (*CacheToGitHubActions)(nil)
|
||||
_ fmt.Stringer = (*CacheToInline)(nil)
|
||||
_ fmt.Stringer = (*CacheToLocal)(nil)
|
||||
_ fmt.Stringer = (*CacheToRegistry)(nil)
|
||||
_ fmt.Stringer = (*CacheToS3)(nil)
|
||||
_ fmt.Stringer = CacheWithCompression{}
|
||||
_ fmt.Stringer = CacheWithIgnoreError{}
|
||||
_ fmt.Stringer = CacheWithMode{}
|
||||
_ fmt.Stringer = CacheWithOCI{}
|
||||
_ infer.Annotated = (*CacheFrom)(nil)
|
||||
_ infer.Annotated = (*CacheFromAzureBlob)(nil)
|
||||
_ infer.Annotated = (*CacheFromGitHubActions)(nil)
|
||||
_ infer.Annotated = (*CacheFromLocal)(nil)
|
||||
_ infer.Annotated = (*CacheFromRegistry)(nil)
|
||||
_ infer.Annotated = (*CacheFromS3)(nil)
|
||||
_ infer.Annotated = (*CacheTo)(nil)
|
||||
_ infer.Annotated = (*CacheToInline)(nil)
|
||||
_ infer.Annotated = (*CacheToLocal)(nil)
|
||||
_ infer.Annotated = (*CacheWithCompression)(nil)
|
||||
_ infer.Annotated = (*CacheWithIgnoreError)(nil)
|
||||
_ infer.Annotated = (*CacheWithMode)(nil)
|
||||
_ infer.Annotated = (*CacheWithOCI)(nil)
|
||||
_ infer.Enum[CacheMode] = (*CacheMode)(nil)
|
||||
_ infer.Enum[CompressionType] = (*CompressionType)(nil)
|
||||
)
|
||||
|
||||
// CacheFromLocal pulls cache manifests from a local directory.
|
||||
type CacheFromLocal struct {
|
||||
Src string `pulumi:"src"`
|
||||
Digest string `pulumi:"digest,optional"`
|
||||
}
|
||||
|
||||
// String returns the CLI-encoded value of these cache options, or an empty
|
||||
// string if the receiver is nil.
|
||||
func (c *CacheFromLocal) String() string {
|
||||
if c == nil {
|
||||
return ""
|
||||
}
|
||||
parts := []string{"type=local"}
|
||||
if c.Src != "" {
|
||||
parts = append(parts, "src="+c.Src)
|
||||
}
|
||||
if c.Digest != "" {
|
||||
parts = append(parts, "digest="+c.Digest)
|
||||
}
|
||||
return strings.Join(parts, ",")
|
||||
}
|
||||
|
||||
// Annotate sets docstrings on CacheFromLocal.
|
||||
func (c *CacheFromLocal) Annotate(a infer.Annotator) {
|
||||
a.Describe(&c.Src, "Path of the local directory where cache gets imported from.")
|
||||
a.Describe(&c.Digest, "Digest of manifest to import.")
|
||||
}
|
||||
|
||||
// CacheFromRegistry pulls cache manifests from a registry ref.
|
||||
type CacheFromRegistry struct {
|
||||
Ref string `pulumi:"ref"`
|
||||
}
|
||||
|
||||
// Annotate sets docstrings on CacheFromRegistry.
|
||||
func (c *CacheFromRegistry) Annotate(a infer.Annotator) {
|
||||
a.Describe(&c.Ref, "Fully qualified name of the cache image to import.")
|
||||
}
|
||||
|
||||
// String returns the CLI-encoded value of these cache options, or an empty
|
||||
// string if the receiver is nil.
|
||||
func (c *CacheFromRegistry) String() string {
|
||||
if c == nil {
|
||||
return ""
|
||||
}
|
||||
return "type=registry,ref=" + c.Ref
|
||||
}
|
||||
|
||||
// CacheWithOCI exposes OCI media type options.
|
||||
type CacheWithOCI struct {
|
||||
OCI *bool `pulumi:"ociMediaTypes,optional"`
|
||||
ImageManifest *bool `pulumi:"imageManifest,optional"`
|
||||
}
|
||||
|
||||
// Annotate sets docstrings on CacheWithOCI.
|
||||
func (c *CacheWithOCI) Annotate(a infer.Annotator) {
|
||||
a.Describe(&c.OCI, dedent(`
|
||||
Whether to use OCI media types in exported manifests. Defaults to
|
||||
"true".
|
||||
`))
|
||||
a.Describe(&c.ImageManifest, dedent(`
|
||||
Export cache manifest as an OCI-compatible image manifest instead of a
|
||||
manifest list. Requires "ociMediaTypes" to also be "true".
|
||||
|
||||
Some registries like AWS ECR will not work with caching if this is
|
||||
"false".
|
||||
|
||||
Defaults to "false" to match Docker's default behavior.
|
||||
`))
|
||||
|
||||
a.SetDefault(&c.OCI, true)
|
||||
a.SetDefault(&c.ImageManifest, false)
|
||||
}
|
||||
|
||||
// String returns the CLI-encoded value of these cache options, or an empty
|
||||
// string if unknown.
|
||||
func (c CacheWithOCI) String() string {
|
||||
if c.OCI == nil {
|
||||
return ""
|
||||
}
|
||||
parts := []string{fmt.Sprintf("oci-mediatypes=%t", *c.OCI)}
|
||||
if c.ImageManifest != nil {
|
||||
parts = append(parts, fmt.Sprintf("image-manifest=%t", *c.ImageManifest))
|
||||
}
|
||||
return strings.Join(parts, ",")
|
||||
}
|
||||
|
||||
// CacheFromGitHubActions pulls cache manifests from the GitHub actions cache.
|
||||
type CacheFromGitHubActions struct {
|
||||
URL string `pulumi:"url,optional"`
|
||||
Token string `pulumi:"token,optional" provider:"secret"`
|
||||
Scope string `pulumi:"scope,optional"`
|
||||
}
|
||||
|
||||
// Annotate sets docstrings on CacheFromGitHubActions.
|
||||
func (c *CacheFromGitHubActions) Annotate(a infer.Annotator) {
|
||||
a.SetDefault(&c.URL, "", "ACTIONS_RUNTIME_URL")
|
||||
a.SetDefault(&c.Token, "", "ACTIONS_RUNTIME_TOKEN")
|
||||
a.SetDefault(&c.Scope, "", "buildkit")
|
||||
|
||||
a.Describe(&c.URL, dedent(`
|
||||
The cache server URL to use for artifacts.
|
||||
|
||||
Defaults to "$ACTIONS_RUNTIME_URL", although a separate action like
|
||||
"crazy-max/ghaction-github-runtime" is recommended to expose this
|
||||
environment variable to your jobs.
|
||||
`))
|
||||
a.Describe(&c.Token, dedent(`
|
||||
The GitHub Actions token to use. This is not a personal access tokens
|
||||
and is typically generated automatically as part of each job.
|
||||
|
||||
Defaults to "$ACTIONS_RUNTIME_TOKEN", although a separate action like
|
||||
"crazy-max/ghaction-github-runtime" is recommended to expose this
|
||||
environment variable to your jobs.
|
||||
|
||||
`))
|
||||
a.Describe(&c.Scope, dedent(`
|
||||
The scope to use for cache keys. Defaults to "buildkit".
|
||||
|
||||
This should be set if building and caching multiple images in one
|
||||
workflow, otherwise caches will overwrite each other.
|
||||
`))
|
||||
}
|
||||
|
||||
func (c *CacheFromGitHubActions) String() string {
|
||||
if c == nil {
|
||||
return ""
|
||||
}
|
||||
parts := []string{"type=gha"}
|
||||
if c.Scope != "" {
|
||||
parts = append(parts, "scope="+c.Scope)
|
||||
}
|
||||
if c.Token != "" {
|
||||
parts = append(parts, "token="+c.Token)
|
||||
}
|
||||
if c.URL != "" {
|
||||
parts = append(parts, "url="+c.URL)
|
||||
}
|
||||
return strings.Join(parts, ",")
|
||||
}
|
||||
|
||||
// CacheFromAzureBlob pulls cache manifests from Azure
|
||||
// blob storage.
|
||||
type CacheFromAzureBlob struct {
|
||||
Name string `pulumi:"name"`
|
||||
AccountURL string `pulumi:"accountUrl,optional"`
|
||||
SecretAccessKey string `pulumi:"secretAccessKey,optional" provider:"secret"`
|
||||
}
|
||||
|
||||
// String returns the CLI-encoded value of these cache options, or an empty
|
||||
// string if the receiver is nil.
|
||||
func (c *CacheFromAzureBlob) String() string {
|
||||
if c == nil {
|
||||
return ""
|
||||
}
|
||||
parts := []string{"type=azblob"}
|
||||
if c.Name != "" {
|
||||
parts = append(parts, "name="+c.Name)
|
||||
}
|
||||
if c.AccountURL != "" {
|
||||
parts = append(parts, "account_url="+c.AccountURL)
|
||||
}
|
||||
if c.SecretAccessKey != "" {
|
||||
parts = append(parts, "secret_access_key="+c.SecretAccessKey)
|
||||
}
|
||||
return strings.Join(parts, ",")
|
||||
}
|
||||
|
||||
// Annotate sets docstrings on CacheFromAzureBlob.
|
||||
func (c *CacheFromAzureBlob) Annotate(a infer.Annotator) {
|
||||
a.Describe(&c.Name, "The name of the cache image.")
|
||||
a.Describe(&c.AccountURL, "Base URL of the storage account.")
|
||||
a.Describe(&c.SecretAccessKey, "Blob storage account key.")
|
||||
}
|
||||
|
||||
// CacheToAzureBlob pushes cache manifests to Azure blob storage.
|
||||
type CacheToAzureBlob struct {
|
||||
CacheWithMode
|
||||
CacheWithIgnoreError
|
||||
|
||||
CacheFromAzureBlob
|
||||
}
|
||||
|
||||
func (c *CacheToAzureBlob) String() string {
|
||||
if c == nil {
|
||||
return ""
|
||||
}
|
||||
return join(&c.CacheFromAzureBlob, c.CacheWithMode, c.CacheWithIgnoreError)
|
||||
}
|
||||
|
||||
// CacheFromS3 pulls cache manifests from S3-compatible APIs.
|
||||
type CacheFromS3 struct {
|
||||
Region string `pulumi:"region"`
|
||||
Bucket string `pulumi:"bucket"`
|
||||
Name string `pulumi:"name,optional"`
|
||||
EndpointURL string `pulumi:"endpointUrl,optional"`
|
||||
BlobsPrefix string `pulumi:"blobsPrefix,optional"`
|
||||
ManifestsPrefix string `pulumi:"manifestsPrefix,optional"`
|
||||
UsePathStyle *bool `pulumi:"usePathStyle,optional"`
|
||||
AccessKeyID string `pulumi:"accessKeyId,optional"`
|
||||
SecretAccessKey string `pulumi:"secretAccessKey,optional" provider:"secret"`
|
||||
SessionToken string `pulumi:"sessionToken,optional" provider:"secret"`
|
||||
}
|
||||
|
||||
// Annotate sets docstrings and defaults on CacheFromS3.
|
||||
func (c *CacheFromS3) Annotate(a infer.Annotator) {
|
||||
a.SetDefault(&c.Region, "", "AWS_REGION")
|
||||
a.SetDefault(&c.AccessKeyID, "", "AWS_ACCESS_KEY_ID")
|
||||
a.SetDefault(&c.SecretAccessKey, "", "AWS_SECRET_ACCESS_KEY")
|
||||
a.SetDefault(&c.SessionToken, "", "AWS_SESSION_TOKEN")
|
||||
|
||||
a.Describe(&c.Bucket, dedent(`
|
||||
Name of the S3 bucket.
|
||||
`))
|
||||
a.Describe(&c.Region, dedent(`
|
||||
The geographic location of the bucket. Defaults to "$AWS_REGION".
|
||||
`))
|
||||
a.Describe(&c.AccessKeyID, dedent(`
|
||||
Defaults to "$AWS_ACCESS_KEY_ID".
|
||||
`))
|
||||
a.Describe(&c.SecretAccessKey, dedent(`
|
||||
Defaults to "$AWS_SECRET_ACCESS_KEY".
|
||||
`))
|
||||
a.Describe(&c.SessionToken, dedent(`
|
||||
Defaults to "$AWS_SESSION_TOKEN".
|
||||
`))
|
||||
a.Describe(&c.BlobsPrefix, dedent(`
|
||||
Prefix to prepend to blob filenames.
|
||||
`))
|
||||
a.Describe(&c.EndpointURL, dedent(`
|
||||
Endpoint of the S3 bucket.
|
||||
`))
|
||||
a.Describe(&c.ManifestsPrefix, dedent(`
|
||||
Prefix to prepend on manifest filenames.
|
||||
`))
|
||||
a.Describe(&c.Name, dedent(`
|
||||
Name of the cache image.
|
||||
`))
|
||||
a.Describe(&c.UsePathStyle, dedent(`
|
||||
Uses "bucket" in the URL instead of hostname when "true".
|
||||
`))
|
||||
}
|
||||
|
||||
// String returns the CLI-encoded value of these cache options, or an empty
|
||||
// string if the receiver is nil.
|
||||
func (c *CacheFromS3) String() string {
|
||||
if c == nil {
|
||||
return ""
|
||||
}
|
||||
parts := []string{"type=s3"}
|
||||
if c.Bucket != "" {
|
||||
parts = append(parts, "bucket="+c.Bucket)
|
||||
}
|
||||
if c.Name != "" {
|
||||
parts = append(parts, "name="+c.Name)
|
||||
}
|
||||
if c.EndpointURL != "" {
|
||||
parts = append(parts, "endpoint_url="+c.EndpointURL)
|
||||
}
|
||||
if c.BlobsPrefix != "" {
|
||||
parts = append(parts, "blobs_prefix="+c.BlobsPrefix)
|
||||
}
|
||||
if c.ManifestsPrefix != "" {
|
||||
parts = append(parts, "manifests_prefix="+c.ManifestsPrefix)
|
||||
}
|
||||
if c.UsePathStyle != nil {
|
||||
parts = append(parts, fmt.Sprintf("use_path_type=%t", *c.UsePathStyle))
|
||||
}
|
||||
if c.AccessKeyID != "" {
|
||||
parts = append(parts, "access_key_id="+c.AccessKeyID)
|
||||
}
|
||||
if c.SecretAccessKey != "" {
|
||||
parts = append(parts, "secret_access_key="+c.SecretAccessKey)
|
||||
}
|
||||
if c.SessionToken != "" {
|
||||
parts = append(parts, "session_token="+c.SessionToken)
|
||||
}
|
||||
|
||||
return strings.Join(parts, ",")
|
||||
}
|
||||
|
||||
// CacheWithMode is a cache that can configure its mode.
|
||||
type CacheWithMode struct {
|
||||
Mode *CacheMode `pulumi:"mode,optional"`
|
||||
}
|
||||
|
||||
// Annotate sets docstrings and defaults on CacheWithMode.
|
||||
func (c *CacheWithMode) Annotate(a infer.Annotator) {
|
||||
m := Min
|
||||
a.SetDefault(&c.Mode, &m)
|
||||
a.Describe(&c.Mode, dedent(`
|
||||
The cache mode to use. Defaults to "min".
|
||||
`))
|
||||
}
|
||||
|
||||
func (c CacheWithMode) String() string {
|
||||
if c.Mode == nil {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("mode=%s", *c.Mode)
|
||||
}
|
||||
|
||||
// CacheWithIgnoreError exposes an option to ignore errors during caching.
|
||||
type CacheWithIgnoreError struct {
|
||||
IgnoreError *bool `pulumi:"ignoreError,optional"`
|
||||
}
|
||||
|
||||
// Annotate sets docstrings and defaults on CacheWithIgnoreError.
|
||||
func (c *CacheWithIgnoreError) Annotate(a infer.Annotator) {
|
||||
a.SetDefault(&c.IgnoreError, false)
|
||||
a.Describe(&c.IgnoreError, "Ignore errors caused by failed cache exports.")
|
||||
}
|
||||
|
||||
func (c CacheWithIgnoreError) String() string {
|
||||
if c.IgnoreError == nil {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("ignore-error=%t", *c.IgnoreError)
|
||||
}
|
||||
|
||||
// CacheToS3 pushes cache manifests to an S3-compatible API.
|
||||
type CacheToS3 struct {
|
||||
CacheWithMode
|
||||
CacheWithIgnoreError
|
||||
|
||||
CacheFromS3
|
||||
}
|
||||
|
||||
// String returns the CLI-encoded value of these cache options, or an empty
|
||||
// string if the receiver is nil.
|
||||
func (c *CacheToS3) String() string {
|
||||
if c == nil {
|
||||
return ""
|
||||
}
|
||||
return join(&c.CacheFromS3, c.CacheWithMode, c.CacheWithIgnoreError)
|
||||
}
|
||||
|
||||
// Raw is a CLI-encoded cache entry appropriate for passing directly to the
|
||||
// CLI. Useful if the Docker backend supports cache types not captured by our
|
||||
// API, or if the user just prefers "type=..." inputs.
|
||||
type Raw string
|
||||
|
||||
// String return the raw string as-is. This can be empty during previews where
|
||||
// the user has provided a value but it is unknown.
|
||||
func (c Raw) String() string {
|
||||
return string(c)
|
||||
}
|
||||
|
||||
// CacheFrom is a "union" type for all of our available `--cache-from` options.
|
||||
type CacheFrom struct {
|
||||
Local *CacheFromLocal `pulumi:"local,optional"`
|
||||
Registry *CacheFromRegistry `pulumi:"registry,optional"`
|
||||
GHA *CacheFromGitHubActions `pulumi:"gha,optional"`
|
||||
AZBlob *CacheFromAzureBlob `pulumi:"azblob,optional"`
|
||||
S3 *CacheFromS3 `pulumi:"s3,optional"`
|
||||
Raw Raw `pulumi:"raw,optional"`
|
||||
|
||||
Disabled bool `pulumi:"disabled,optional"`
|
||||
}
|
||||
|
||||
// Annotate sets docstrings and defaults on CacheFrom.
|
||||
func (c *CacheFrom) Annotate(a infer.Annotator) {
|
||||
a.Describe(&c.Local, dedent(`
|
||||
A simple backend which caches images on your local filesystem.
|
||||
`))
|
||||
a.Describe(&c.Registry, dedent(`
|
||||
Upload build caches to remote registries.
|
||||
`))
|
||||
a.Describe(&c.GHA, dedent(`
|
||||
Recommended for use with GitHub Actions workflows.
|
||||
|
||||
An action like "crazy-max/ghaction-github-runtime" is recommended to
|
||||
expose appropriate credentials to your GitHub workflow.
|
||||
`))
|
||||
a.Describe(&c.AZBlob, dedent(`
|
||||
Upload build caches to Azure's blob storage service.
|
||||
`))
|
||||
a.Describe(&c.S3, dedent(`
|
||||
Upload build caches to AWS S3 or an S3-compatible services such as
|
||||
MinIO.
|
||||
`))
|
||||
a.Describe(&c.Raw, dedent(`
|
||||
A raw string as you would provide it to the Docker CLI (e.g.,
|
||||
"type=inline").
|
||||
`))
|
||||
|
||||
a.Describe(&c.Disabled, dedent(`
|
||||
When "true" this entry will be excluded. Defaults to "false".
|
||||
`))
|
||||
}
|
||||
|
||||
// String returns a CLI-encoded value for this `--cache-from` entry, or an
|
||||
// empty string if disabled. `validate` should be called to ensure only one
|
||||
// entry was set.
|
||||
func (c CacheFrom) String() string {
|
||||
if c.Disabled {
|
||||
return ""
|
||||
}
|
||||
return join(c.Local, c.Registry, c.GHA, c.AZBlob, c.S3, c.Raw)
|
||||
}
|
||||
|
||||
func (c CacheFrom) validate(preview bool) (*controllerapi.CacheOptionsEntry, error) {
|
||||
if strings.Count(c.String(), "type=") > 1 {
|
||||
return nil, errors.New("cacheFrom should only specify one cache type")
|
||||
}
|
||||
parsed, err := buildflags.ParseCacheEntry([]string{c.String()})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(parsed) == 0 {
|
||||
// This can happen for example if we have a GHA cache but no GitHub
|
||||
// environment variables set.
|
||||
// Shouldn't happen...
|
||||
return nil, nil
|
||||
}
|
||||
return parsed[0], nil
|
||||
}
|
||||
|
||||
// CacheToInline embeds cache information directly into an image.
|
||||
type CacheToInline struct{}
|
||||
|
||||
// String returns the CLI-encoded value of these cache options, or an empty
|
||||
// string if unknown.
|
||||
func (c *CacheToInline) String() string {
|
||||
if c == nil {
|
||||
return ""
|
||||
}
|
||||
return "type=inline"
|
||||
}
|
||||
|
||||
// Annotate sets docstrings on CacheToInline.
|
||||
func (c *CacheToInline) Annotate(a infer.Annotator) {
|
||||
a.Describe(&c, dedent(`
|
||||
Include an inline cache with the exported image.
|
||||
`))
|
||||
}
|
||||
|
||||
// CacheToLocal writes cache manifests to a local directory.
|
||||
type CacheToLocal struct {
|
||||
CacheWithCompression
|
||||
CacheWithIgnoreError
|
||||
CacheWithMode
|
||||
|
||||
Dest string `pulumi:"dest"`
|
||||
}
|
||||
|
||||
// Annotate sets docstrings on CacheToLocal.
|
||||
func (c *CacheToLocal) Annotate(a infer.Annotator) {
|
||||
a.Describe(&c.Dest, dedent(`
|
||||
Path of the local directory to export the cache.
|
||||
`))
|
||||
}
|
||||
|
||||
// String returns the CLI-encoded value of these cache options, or an empty
|
||||
// string if the receiver is nil.
|
||||
func (c *CacheToLocal) String() string {
|
||||
if c == nil {
|
||||
return ""
|
||||
}
|
||||
return join(
|
||||
Raw("type=local,dest="+c.Dest),
|
||||
c.CacheWithCompression,
|
||||
c.CacheWithIgnoreError,
|
||||
)
|
||||
}
|
||||
|
||||
// CacheToRegistry pushes cache manifests to a remote registry.
|
||||
type CacheToRegistry struct {
|
||||
CacheWithMode
|
||||
CacheWithIgnoreError
|
||||
CacheWithOCI
|
||||
CacheWithCompression
|
||||
|
||||
CacheFromRegistry
|
||||
}
|
||||
|
||||
// String returns the CLI-encoded value of these cache options, or an empty
|
||||
// string if the receiver is nil.
|
||||
func (c *CacheToRegistry) String() string {
|
||||
if c == nil {
|
||||
return ""
|
||||
}
|
||||
return join(
|
||||
&c.CacheFromRegistry,
|
||||
c.CacheWithMode,
|
||||
c.CacheWithIgnoreError,
|
||||
c.CacheWithOCI,
|
||||
c.CacheWithCompression,
|
||||
)
|
||||
}
|
||||
|
||||
// CacheWithCompression is a cache with options to configure compression
|
||||
// settings.
|
||||
type CacheWithCompression struct {
|
||||
Compression *CompressionType `pulumi:"compression,optional"`
|
||||
CompressionLevel int `pulumi:"compressionLevel,optional"`
|
||||
ForceCompression *bool `pulumi:"forceCompression,optional"`
|
||||
}
|
||||
|
||||
// Annotate sets docstrings and defaults on CacheWithCompression.
|
||||
func (c *CacheWithCompression) Annotate(a infer.Annotator) {
|
||||
gz := Gzip
|
||||
a.SetDefault(&c.Compression, &gz)
|
||||
a.SetDefault(&c.CompressionLevel, 0)
|
||||
a.SetDefault(&c.ForceCompression, false)
|
||||
|
||||
a.Describe(&c.Compression, "The compression type to use.")
|
||||
a.Describe(&c.CompressionLevel, "Compression level from 0 to 22.")
|
||||
a.Describe(&c.ForceCompression, "Forcefully apply compression.")
|
||||
}
|
||||
|
||||
// String returns the CLI-encoded value of these cache options, or an empty
|
||||
// string if the receiver is nil.
|
||||
func (c CacheWithCompression) String() string {
|
||||
if c.CompressionLevel == 0 {
|
||||
return ""
|
||||
}
|
||||
parts := []string{}
|
||||
if c.Compression != nil {
|
||||
parts = append(parts, fmt.Sprintf("compression=%s", *c.Compression))
|
||||
}
|
||||
if c.CompressionLevel > 0 {
|
||||
cl := c.CompressionLevel
|
||||
if cl > 22 {
|
||||
cl = 22
|
||||
}
|
||||
parts = append(parts, fmt.Sprintf("compression-level=%d", cl))
|
||||
}
|
||||
if c.ForceCompression != nil {
|
||||
parts = append(parts, fmt.Sprintf("force-compression=%t", *c.ForceCompression))
|
||||
}
|
||||
return strings.Join(parts, ",")
|
||||
}
|
||||
|
||||
// CacheToGitHubActions pushes cache manifests to the GitHub Actions cache
|
||||
// backend.
|
||||
type CacheToGitHubActions struct {
|
||||
CacheWithMode
|
||||
CacheWithIgnoreError
|
||||
|
||||
CacheFromGitHubActions
|
||||
}
|
||||
|
||||
// String returns the CLI-encoded value of these cache options, or an empty
|
||||
// string if the receiver is nil.
|
||||
func (c *CacheToGitHubActions) String() string {
|
||||
if c == nil {
|
||||
return ""
|
||||
}
|
||||
return join(&c.CacheFromGitHubActions, c.CacheWithMode, c.CacheWithIgnoreError)
|
||||
}
|
||||
|
||||
// CacheTo is a "union" type for all of our available `--cache-to` options.
|
||||
type CacheTo struct {
|
||||
Inline *CacheToInline `pulumi:"inline,optional"`
|
||||
Local *CacheToLocal `pulumi:"local,optional"`
|
||||
Registry *CacheToRegistry `pulumi:"registry,optional"`
|
||||
GHA *CacheToGitHubActions `pulumi:"gha,optional"`
|
||||
AZBlob *CacheToAzureBlob `pulumi:"azblob,optional"`
|
||||
S3 *CacheToS3 `pulumi:"s3,optional"`
|
||||
Raw Raw `pulumi:"raw,optional"`
|
||||
|
||||
Disabled bool `pulumi:"disabled,optional"`
|
||||
}
|
||||
|
||||
// Annotate sets docstrings and defaults on CacheTo.
|
||||
func (c *CacheTo) Annotate(a infer.Annotator) {
|
||||
a.Describe(&c.Inline, dedent(`
|
||||
The inline cache storage backend is the simplest implementation to get
|
||||
started with, but it does not handle multi-stage builds. Consider the
|
||||
"registry" cache backend instead.
|
||||
`))
|
||||
a.Describe(&c.Local, dedent(`
|
||||
A simple backend which caches imagines on your local filesystem.
|
||||
`))
|
||||
a.Describe(&c.Registry, dedent(`
|
||||
Push caches to remote registries. Incompatible with the "docker" build
|
||||
driver.
|
||||
`))
|
||||
a.Describe(&c.GHA, dedent(`
|
||||
Recommended for use with GitHub Actions workflows.
|
||||
|
||||
An action like "crazy-max/ghaction-github-runtime" is recommended to
|
||||
expose appropriate credentials to your GitHub workflow.
|
||||
`))
|
||||
a.Describe(&c.AZBlob, dedent(`
|
||||
Push cache to Azure's blob storage service.
|
||||
`))
|
||||
a.Describe(&c.S3, dedent(`
|
||||
Push cache to AWS S3 or S3-compatible services such as MinIO.
|
||||
`))
|
||||
a.Describe(&c.Raw, dedent(`
|
||||
A raw string as you would provide it to the Docker CLI (e.g.,
|
||||
"type=inline")`,
|
||||
))
|
||||
|
||||
a.Describe(&c.Disabled, dedent(`
|
||||
When "true" this entry will be excluded. Defaults to "false".
|
||||
`))
|
||||
}
|
||||
|
||||
// String returns a CLI-encoded value for this `--cache-to` entry, or an
|
||||
// empty string if disabled. `validate` should be called to ensure only one
|
||||
// entry was set.
|
||||
func (c CacheTo) String() string {
|
||||
if c.Disabled {
|
||||
return ""
|
||||
}
|
||||
return join(c.Inline, c.Local, c.Registry, c.GHA, c.AZBlob, c.S3, c.Raw)
|
||||
}
|
||||
|
||||
func (c CacheTo) validate(preview bool) (*controllerapi.CacheOptionsEntry, error) {
|
||||
if strings.Count(c.String(), "type=") > 1 {
|
||||
return nil, errors.New("cacheTo should only specify one cache type")
|
||||
}
|
||||
parsed, err := buildflags.ParseCacheEntry([]string{c.String()})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(parsed) == 0 {
|
||||
// This can happen for example if we have a GHA cache but no GitHub
|
||||
// environment variables set.
|
||||
// Shouldn't happen...
|
||||
return nil, nil
|
||||
}
|
||||
return parsed[0], nil
|
||||
}
|
||||
|
||||
// CacheMode controls the complexity of exported cache manifests.
|
||||
type CacheMode string
|
||||
|
||||
const (
|
||||
Min CacheMode = "min" // Min cache mode.
|
||||
Max CacheMode = "max" // Max cache mode.
|
||||
)
|
||||
|
||||
// Values returns all valid CacheMode values for SDK generation.
|
||||
func (CacheMode) Values() []infer.EnumValue[CacheMode] {
|
||||
return []infer.EnumValue[CacheMode]{
|
||||
{
|
||||
Value: Min,
|
||||
Description: "Only layers that are exported into the resulting image are cached.",
|
||||
},
|
||||
{
|
||||
Value: Max,
|
||||
Description: "All layers are cached, even those of intermediate steps.",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// CompressionType is the algorithm used for compressing blobs.
|
||||
type CompressionType string
|
||||
|
||||
const (
|
||||
Gzip CompressionType = "gzip" // Gzip compression.
|
||||
Estargz CompressionType = "estargz" // Estargz compression.
|
||||
Zstd CompressionType = "zstd" // Zstd compression.
|
||||
)
|
||||
|
||||
// Values returns all valid CompressionType values for SDK generation.
|
||||
func (CompressionType) Values() []infer.EnumValue[CompressionType] {
|
||||
return []infer.EnumValue[CompressionType]{
|
||||
{Value: Gzip, Description: "Use `gzip` for compression."},
|
||||
{Value: Estargz, Description: "Use `estargz` for compression."},
|
||||
{Value: Zstd, Description: "Use `zstd` for compression."},
|
||||
}
|
||||
}
|
||||
|
||||
type joiner struct{ sep string }
|
||||
|
||||
func (j joiner) join(ss ...fmt.Stringer) string {
|
||||
parts := []string{}
|
||||
for _, s := range ss {
|
||||
p := s.String()
|
||||
if p == "" {
|
||||
continue
|
||||
}
|
||||
parts = append(parts, p)
|
||||
}
|
||||
return strings.Join(parts, j.sep)
|
||||
}
|
||||
|
||||
func join(ss ...fmt.Stringer) string {
|
||||
return joiner{","}.join(ss...)
|
||||
}
|
||||
139
provider/internal/cache_test.go
Normal file
139
provider/internal/cache_test.go
Normal file
@@ -0,0 +1,139 @@
|
||||
// Copyright 2024, 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 internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/buildx/util/buildflags"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
|
||||
)
|
||||
|
||||
func TestCacheString(t *testing.T) {
|
||||
t.Parallel()
|
||||
gzip := Gzip
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
given fmt.Stringer
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "s3",
|
||||
given: CacheTo{S3: &CacheToS3{
|
||||
CacheFromS3: CacheFromS3{
|
||||
Region: "us-west-2",
|
||||
Bucket: "bucket-foo",
|
||||
Name: "myname",
|
||||
EndpointURL: "https://some.endpoint",
|
||||
BlobsPrefix: "blob-prefix",
|
||||
ManifestsPrefix: "manifest-prefix",
|
||||
UsePathStyle: pulumi.BoolRef(true),
|
||||
AccessKeyID: "access-key-id",
|
||||
SecretAccessKey: "secret-key",
|
||||
SessionToken: "session",
|
||||
},
|
||||
}},
|
||||
//nolint:lll // Taken from AWS reference docs.
|
||||
want: "type=s3,bucket=bucket-foo,name=myname,endpoint_url=https://some.endpoint,blobs_prefix=blob-prefix,manifests_prefix=manifest-prefix,use_path_type=true,access_key_id=access-key-id,secret_access_key=secret-key,session_token=session",
|
||||
},
|
||||
{
|
||||
name: "gha",
|
||||
given: CacheTo{GHA: &CacheToGitHubActions{}},
|
||||
want: "type=gha",
|
||||
},
|
||||
{
|
||||
name: "from-local",
|
||||
given: CacheFrom{Local: &CacheFromLocal{Src: "/foo/bar"}},
|
||||
want: "type=local,src=/foo/bar",
|
||||
},
|
||||
{
|
||||
name: "to-local",
|
||||
given: CacheTo{Local: &CacheToLocal{Dest: "/foo/bar"}},
|
||||
want: "type=local,dest=/foo/bar",
|
||||
},
|
||||
{
|
||||
name: "inline",
|
||||
given: CacheTo{Inline: &CacheToInline{}},
|
||||
want: "type=inline",
|
||||
},
|
||||
{
|
||||
name: "raw",
|
||||
given: CacheTo{Raw: Raw("type=gha")},
|
||||
want: "type=gha",
|
||||
},
|
||||
{
|
||||
name: "compression",
|
||||
given: CacheTo{Local: &CacheToLocal{
|
||||
Dest: "/foo",
|
||||
CacheWithCompression: CacheWithCompression{
|
||||
Compression: &gzip,
|
||||
CompressionLevel: 100,
|
||||
ForceCompression: pulumi.BoolRef(true),
|
||||
},
|
||||
}},
|
||||
want: "type=local,dest=/foo,compression=gzip,compression-level=22,force-compression=true",
|
||||
},
|
||||
{
|
||||
name: "ignore-error",
|
||||
given: CacheTo{
|
||||
AZBlob: &CacheToAzureBlob{
|
||||
CacheWithIgnoreError: CacheWithIgnoreError{pulumi.BoolRef(true)},
|
||||
},
|
||||
},
|
||||
want: "type=azblob,ignore-error=true",
|
||||
},
|
||||
{
|
||||
name: "oci",
|
||||
given: CacheTo{
|
||||
Registry: &CacheToRegistry{
|
||||
CacheFromRegistry: CacheFromRegistry{Ref: "docker.io/foo/bar:baz"},
|
||||
CacheWithOCI: CacheWithOCI{
|
||||
OCI: pulumi.BoolRef(true),
|
||||
ImageManifest: pulumi.BoolRef(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
want: "type=registry,ref=docker.io/foo/bar:baz,oci-mediatypes=true,image-manifest=true",
|
||||
},
|
||||
{
|
||||
name: "disabled-to",
|
||||
given: CacheTo{
|
||||
Raw: Raw("type=gha"),
|
||||
Disabled: true,
|
||||
},
|
||||
want: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
actual := tt.given.String()
|
||||
assert.Equal(t, tt.want, actual)
|
||||
|
||||
if tt.want != "" {
|
||||
// Our output should be parsable by Docker.
|
||||
_, err := buildflags.ParseCacheEntry([]string{actual})
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
420
provider/internal/cli.go
Normal file
420
provider/internal/cli.go
Normal file
@@ -0,0 +1,420 @@
|
||||
// Copyright 2024, 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.
|
||||
|
||||
//go:generate go run go.uber.org/mock/mockgen -typed -package internal -source cli.go -destination mockcli_test.go --self_package github.com/pulumi/pulumi-docker-build/provider/internal
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/buildx/commands"
|
||||
"github.com/docker/cli/cli-plugins/manager"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/config/credentials"
|
||||
cfgtypes "github.com/docker/cli/cli/config/types"
|
||||
"github.com/docker/cli/cli/streams"
|
||||
"github.com/moby/buildkit/client"
|
||||
cp "github.com/otiai10/copy"
|
||||
"github.com/regclient/regclient"
|
||||
"github.com/regclient/regclient/config"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
provider "github.com/pulumi/pulumi-go-provider"
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/common/diag"
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
|
||||
)
|
||||
|
||||
// cli wraps a DockerCLI instance with scoped auth credentials. It satisfies
|
||||
// the Cli interface so it can be used with Docker's cobra.Commands directly.
|
||||
//
|
||||
// It buffers stdout/stderr, and layers temporary auth configs on top of the
|
||||
// host's existing auth.
|
||||
type cli struct {
|
||||
command.Cli
|
||||
|
||||
auths map[string]cfgtypes.AuthConfig
|
||||
host *host
|
||||
|
||||
in string // stdin
|
||||
r, w *os.File // stdout
|
||||
err bytes.Buffer // stderr
|
||||
dumplogs bool // if true then tail() will re-log status messages
|
||||
done chan struct{} // signaled when all logs have been forwarded to the engine.
|
||||
}
|
||||
|
||||
// Cli wraps the Docker interface for mock generation.
|
||||
type Cli interface {
|
||||
command.Cli
|
||||
}
|
||||
|
||||
// wrap creates a new cli client with auth configs layered on top of our host's
|
||||
// auth. Repeated auth for the same host will take precedence over earlier
|
||||
// credentials.
|
||||
func wrap(host *host, registries ...Registry) (*cli, error) {
|
||||
// We need to create a new DockerCLI instance because we don't want the
|
||||
// auth changes we make to the ConfigFile to leak to the host.
|
||||
docker, err := newDockerCLI(host.config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
auths := map[string]cfgtypes.AuthConfig{}
|
||||
for k, v := range host.auths {
|
||||
if k != config.DockerRegistryAuth {
|
||||
k = credentials.ConvertToHostname(k)
|
||||
}
|
||||
auths[k] = cfgtypes.AuthConfig{
|
||||
ServerAddress: v.ServerAddress,
|
||||
Username: v.Username,
|
||||
Password: v.Password,
|
||||
}
|
||||
}
|
||||
|
||||
for _, r := range registries {
|
||||
// HostNewName takes care of DockerHub's special-casing for us.
|
||||
h := config.HostNewName(credentials.ConvertToHostname(r.Address))
|
||||
key := h.CredHost
|
||||
if key == "" {
|
||||
key = h.Hostname
|
||||
}
|
||||
|
||||
auths[key] = cfgtypes.AuthConfig{
|
||||
ServerAddress: h.Hostname,
|
||||
Username: r.Username,
|
||||
Password: r.Password,
|
||||
}
|
||||
}
|
||||
|
||||
// Override our config's auth and disable any credential helpers. Auth
|
||||
// lookups will now only return whatever we have in memory.
|
||||
cfg := docker.ConfigFile()
|
||||
cfg.AuthConfigs = auths
|
||||
cfg.CredentialHelpers = nil
|
||||
cfg.CredentialsStore = ""
|
||||
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wrapped := &cli{
|
||||
Cli: docker,
|
||||
host: host,
|
||||
auths: auths,
|
||||
r: r,
|
||||
w: w,
|
||||
}
|
||||
|
||||
return wrapped, nil
|
||||
}
|
||||
|
||||
func (c *cli) In() *streams.In {
|
||||
return streams.NewIn(io.NopCloser(strings.NewReader(c.in)))
|
||||
}
|
||||
|
||||
func (c *cli) Out() *streams.Out {
|
||||
return streams.NewOut(c.w)
|
||||
}
|
||||
|
||||
func (c *cli) Err() io.Writer {
|
||||
return &c.err
|
||||
}
|
||||
|
||||
// rc returns a registry client with matching auth.
|
||||
func (c *cli) rc() *regclient.RegClient {
|
||||
hosts := []config.Host{}
|
||||
for k, v := range c.auths {
|
||||
h := config.HostNewName(k)
|
||||
h.User = v.Username
|
||||
h.Pass = v.Password
|
||||
hosts = append(hosts, *h)
|
||||
}
|
||||
return regclient.New(
|
||||
regclient.WithConfigHost(hosts...),
|
||||
)
|
||||
}
|
||||
|
||||
// tail is meant to be called as a goroutine and will pipe output from the CLI
|
||||
// back to the Pulumi engine. Requires a corresponding call to close.
|
||||
func (c *cli) tail(ctx provider.Context) {
|
||||
c.done = make(chan struct{}, 1)
|
||||
defer func() {
|
||||
c.done <- struct{}{}
|
||||
if err := recover(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "recovered: %s\n", err)
|
||||
}
|
||||
}()
|
||||
|
||||
b := bytes.Buffer{}
|
||||
|
||||
s := bufio.NewScanner(c.r)
|
||||
for s.Scan() {
|
||||
text := s.Text()
|
||||
ctx.LogStatus(diag.Info, text)
|
||||
_, _ = b.WriteString(text + "\n")
|
||||
}
|
||||
ctx.LogStatus(diag.Info, "") // clear confusing "DONE" statements.
|
||||
|
||||
if c.dumplogs {
|
||||
// Persist the full Docker output on error for easier debugging.
|
||||
if b.Len() > 0 {
|
||||
ctx.Log(diag.Info, b.String())
|
||||
}
|
||||
if c.err.Len() > 0 {
|
||||
ctx.Log(diag.Error, c.err.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// close flushes any outstanding logs and cleans up resources.
|
||||
func (c *cli) Close() error {
|
||||
err := errors.Join(c.w.Close(), c.r.Close())
|
||||
if c.done != nil {
|
||||
<-c.done
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// execBuild performs a build by os.Exec'ing the docker-buildx binary.
|
||||
// Credentials are communicated to docker-buildx via a temporary directory.
|
||||
// Secrets are communicated via dynamic environment variables.
|
||||
func (c *cli) execBuild(b Build) (*client.SolveResponse, error) {
|
||||
// Setup a temporary directory for auth, and clean it up when we're done.
|
||||
tmp, err := os.MkdirTemp("", "pulumi-docker-")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer contract.IgnoreError(os.RemoveAll(tmp))
|
||||
|
||||
opts := b.BuildOptions()
|
||||
|
||||
builder, err := c.host.builderFor(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Docker expects a "$DOCKER_CONFIG/contexts" directory in addition to
|
||||
// "$DOCKER_CONFIG/config.json", so we attempt to copy this from the host
|
||||
// to our temporary directory. This doesn't always exist, so we ignore errors.
|
||||
hostConfigDir := filepath.Dir(c.ConfigFile().Filename)
|
||||
_ = cp.Copy(
|
||||
filepath.Join(hostConfigDir, "contexts"),
|
||||
filepath.Join(tmp, "contexts"),
|
||||
)
|
||||
|
||||
// Save our temporary credentials to $tmp/config.json.
|
||||
tmpCfg := filepath.Join(tmp, filepath.Base(c.ConfigFile().Filename))
|
||||
c.ConfigFile().Filename = tmpCfg
|
||||
err = c.ConfigFile().Save()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We will spawn docker-buildx with DOCKER_CONFIG set to our temporary
|
||||
// directory for auth, but BUILDX_CONFIG will point to the host. There's a
|
||||
// bunch of builder state in there that we want to preserve.
|
||||
env := []string{
|
||||
"DOCKER_CONFIG=" + tmp,
|
||||
"BUILDX_CONFIG=" + filepath.Join(hostConfigDir, "buildx"),
|
||||
}
|
||||
|
||||
// We need to write to this file in order to recover information about the
|
||||
// build, like the digest.
|
||||
metadata := filepath.Clean(filepath.Join(tmp, "metadata.json"))
|
||||
args := []string{
|
||||
"buildx",
|
||||
"build",
|
||||
"--progress", "plain",
|
||||
"--metadata-file", metadata,
|
||||
"--builder", builder.name,
|
||||
}
|
||||
|
||||
// TODO: --allow
|
||||
// TODO: --annotation
|
||||
// TODO: --attest
|
||||
// TODO: --cgroup-parent
|
||||
|
||||
for k, v := range opts.BuildArgs {
|
||||
args = append(args, "--build-arg", fmt.Sprintf("%s=%s", k, v))
|
||||
}
|
||||
if opts.Builder != "" {
|
||||
args = append(args, "--builder", opts.Builder)
|
||||
}
|
||||
for _, c := range opts.CacheFrom {
|
||||
args = append(args, "--cache-from", attrcsv(c.Type, c.Attrs))
|
||||
}
|
||||
for _, c := range opts.CacheTo {
|
||||
args = append(args, "--cache-to", attrcsv(c.Type, c.Attrs))
|
||||
}
|
||||
if opts.ExportLoad {
|
||||
args = append(args, "--load")
|
||||
}
|
||||
if opts.ExportPush {
|
||||
args = append(args, "--push")
|
||||
}
|
||||
for _, e := range opts.Exports {
|
||||
args = append(args, "--output", attrcsv(e.Type, e.Attrs))
|
||||
}
|
||||
for _, h := range opts.ExtraHosts {
|
||||
args = append(args, "--add-host", h)
|
||||
}
|
||||
for k, v := range opts.NamedContexts {
|
||||
args = append(args, "--build-context", fmt.Sprintf("%s=%s", k, v))
|
||||
}
|
||||
for k, v := range opts.Labels {
|
||||
args = append(args, "--label", fmt.Sprintf("%s=%s", k, v))
|
||||
}
|
||||
if opts.NetworkMode != "" {
|
||||
args = append(args, "--network", opts.NetworkMode)
|
||||
}
|
||||
if opts.NoCache {
|
||||
args = append(args, "--no-cache")
|
||||
}
|
||||
for _, p := range opts.Platforms {
|
||||
args = append(args, "--platform", p)
|
||||
}
|
||||
if opts.Pull {
|
||||
args = append(args, "--pull")
|
||||
}
|
||||
for _, ssh := range opts.SSH {
|
||||
s := ssh.ID
|
||||
if len(ssh.Paths) > 0 {
|
||||
s += "=" + strings.Join(ssh.Paths, ",")
|
||||
}
|
||||
args = append(args, "--ssh", s)
|
||||
}
|
||||
for _, t := range opts.Tags {
|
||||
args = append(args, "--tag", t)
|
||||
}
|
||||
if opts.Target != "" {
|
||||
args = append(args, "--target", opts.Target)
|
||||
}
|
||||
if opts.DockerfileName != "" {
|
||||
args = append(args, "-f", opts.DockerfileName)
|
||||
}
|
||||
if in := b.Inline(); in != "" {
|
||||
c.in = in
|
||||
args = append(args, "-f", "-")
|
||||
}
|
||||
if opts.ContextPath != "" {
|
||||
args = append(args, opts.ContextPath)
|
||||
}
|
||||
|
||||
// We pass secrets by value via dynamic PULUMI_DOCKER_* environment
|
||||
// variables.
|
||||
for _, s := range opts.Secrets {
|
||||
envvar, err := resource.NewUniqueHex("PULUMI_DOCKER_", 0, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// We abuse the pb.Secret proto by stuffing the secret's value in
|
||||
// XXX_unrecognized. We never serialize this proto so this is tolerable.
|
||||
env = append(env, fmt.Sprintf("%s=%s", envvar, s.XXX_unrecognized))
|
||||
args = append(args, "--secret", fmt.Sprintf("id=%s,env=%s", s.ID, envvar))
|
||||
}
|
||||
|
||||
// Invoke docker-buildx.
|
||||
err = c.exec(args, env)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Read the metadata file and transform it back into the map[string]string
|
||||
// structure originally returned by the exporter.
|
||||
_, err = os.Stat(metadata)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("missing metadata: %w", err)
|
||||
}
|
||||
out, err := os.ReadFile(metadata)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var raw map[string]any
|
||||
err = json.Unmarshal(out, &raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp := map[string]string{}
|
||||
for k, v := range raw {
|
||||
switch vv := v.(type) {
|
||||
case string:
|
||||
resp[k] = vv
|
||||
default:
|
||||
out, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
resp[k] = string(out)
|
||||
}
|
||||
}
|
||||
|
||||
return &client.SolveResponse{ExporterResponse: resp}, nil
|
||||
}
|
||||
|
||||
// exec invokes a Docker plugin binary. The first argument should be the name
|
||||
// of the plugin's subcommand, e.g. "buildx".
|
||||
func (c *cli) exec(args, extraEnv []string) error {
|
||||
if len(args) == 0 {
|
||||
return errors.New("args must be non-empty")
|
||||
}
|
||||
name := args[0]
|
||||
|
||||
root := commands.NewRootCmd(name, false, c)
|
||||
plug, err := manager.GetPlugin(name, c, root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if plug.Err != nil {
|
||||
return plug.Err
|
||||
}
|
||||
|
||||
defer contract.IgnoreClose(c.w)
|
||||
|
||||
cmd, err := manager.PluginRunCommand(c, name, root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd.Args = append([]string{cmd.Args[0]}, args...)
|
||||
cmd.Stderr = c.Err()
|
||||
cmd.Stdout = c.Out()
|
||||
cmd.Stdin = c.In()
|
||||
|
||||
cmd.Env = append(cmd.Env, extraEnv...)
|
||||
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
// attrcsv transforms key/values into a CSV: key1=value1,key2=value2,...
|
||||
func attrcsv(typ string, m map[string]string) string {
|
||||
s := []string{"type=" + typ}
|
||||
for k, v := range m {
|
||||
s = append(s, fmt.Sprintf("%s=%s", k, v))
|
||||
}
|
||||
return strings.Join(s, ",")
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Disable the CLI's tendency to log randomly to stdout.
|
||||
logrus.SetOutput(io.Discard)
|
||||
}
|
||||
108
provider/internal/cli_test.go
Normal file
108
provider/internal/cli_test.go
Normal file
@@ -0,0 +1,108 @@
|
||||
// Copyright 2024, 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 internal
|
||||
|
||||
import (
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/cli/config/types"
|
||||
"github.com/regclient/regclient/config"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestExec(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
h, err := newHost(nil)
|
||||
require.NoError(t, err)
|
||||
cli, err := wrap(h)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = cli.exec([]string{"buildx", "version"}, nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
out, err := io.ReadAll(cli.r)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, string(out), "github.com/docker/buildx")
|
||||
}
|
||||
|
||||
func TestWrappedAuth(t *testing.T) {
|
||||
t.Parallel()
|
||||
ecr := "https://1234.dkr.ecr.us-west-2.amazonaws.com"
|
||||
|
||||
realhost, err := newHost(nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
h := &host{
|
||||
auths: map[string]types.AuthConfig{
|
||||
ecr: {
|
||||
Username: "host-aws-user",
|
||||
Password: "host-aws-password",
|
||||
ServerAddress: ecr,
|
||||
},
|
||||
"https://misc": { // Legacy config includes http/https scheme.
|
||||
Username: "host-misc-user",
|
||||
Password: "host-misc-password",
|
||||
ServerAddress: "misc",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
registries := []Registry{
|
||||
{
|
||||
Address: "1234.dkr.ecr.us-west-2.amazonaws.com",
|
||||
Username: "resource-aws-user",
|
||||
Password: "resource-aws-password",
|
||||
},
|
||||
{
|
||||
Address: "docker.io",
|
||||
Username: "resource-dockerhub-user",
|
||||
Password: "resource-dockerhub-password",
|
||||
},
|
||||
}
|
||||
|
||||
_, err = wrap(h, registries...)
|
||||
require.NoError(t, err)
|
||||
|
||||
cli, err := wrap(h, registries...)
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := map[string]types.AuthConfig{
|
||||
"1234.dkr.ecr.us-west-2.amazonaws.com": {
|
||||
Username: "resource-aws-user",
|
||||
Password: "resource-aws-password",
|
||||
ServerAddress: "1234.dkr.ecr.us-west-2.amazonaws.com",
|
||||
},
|
||||
config.DockerRegistryAuth: {
|
||||
Username: "resource-dockerhub-user",
|
||||
Password: "resource-dockerhub-password",
|
||||
ServerAddress: config.DockerRegistryDNS,
|
||||
},
|
||||
"misc": {
|
||||
Username: "host-misc-user",
|
||||
Password: "host-misc-password",
|
||||
ServerAddress: "misc",
|
||||
},
|
||||
}
|
||||
assert.Equal(t, expected, cli.auths)
|
||||
assert.Len(t, h.auths, 2) // In-memory host auth is unchanged.
|
||||
|
||||
// Assert that our on-disk host's auth is untouched.
|
||||
realhostRefreshed, err := newHost(nil)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, realhost.auths, realhostRefreshed.auths)
|
||||
}
|
||||
367
provider/internal/client.go
Normal file
367
provider/internal/client.go
Normal file
@@ -0,0 +1,367 @@
|
||||
// Copyright 2024, 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.
|
||||
|
||||
//go:generate go run go.uber.org/mock/mockgen -typed -package internal -source client.go -destination mockclient_test.go --self_package github.com/pulumi/pulumi-docker-build/provider/internal
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/distribution/reference"
|
||||
buildx "github.com/docker/buildx/build"
|
||||
"github.com/docker/buildx/commands"
|
||||
controllerapi "github.com/docker/buildx/controller/pb"
|
||||
"github.com/docker/buildx/util/dockerutil"
|
||||
"github.com/docker/buildx/util/platformutil"
|
||||
"github.com/docker/buildx/util/progress"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/flags"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
"github.com/moby/buildkit/client"
|
||||
"github.com/moby/buildkit/session"
|
||||
"github.com/moby/buildkit/session/auth/authprovider"
|
||||
"github.com/moby/buildkit/util/progress/progressui"
|
||||
"github.com/regclient/regclient/types/descriptor"
|
||||
"github.com/regclient/regclient/types/errs"
|
||||
"github.com/regclient/regclient/types/manifest"
|
||||
"github.com/regclient/regclient/types/ref"
|
||||
|
||||
provider "github.com/pulumi/pulumi-go-provider"
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/common/diag"
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
|
||||
)
|
||||
|
||||
// Client handles all our Docker API calls.
|
||||
type Client interface {
|
||||
Build(ctx provider.Context, b Build) (*client.SolveResponse, error)
|
||||
BuildKitEnabled() (bool, error)
|
||||
Inspect(ctx context.Context, id string) ([]descriptor.Descriptor, error)
|
||||
Delete(ctx context.Context, id string) error
|
||||
|
||||
ManifestCreate(ctx provider.Context, push bool, target string, refs ...string) error
|
||||
ManifestInspect(ctx provider.Context, target string) (string, error)
|
||||
ManifestDelete(ctx provider.Context, target string) error
|
||||
}
|
||||
|
||||
// Build encapsulates all of the user-provider build parameters and options.
|
||||
type Build interface {
|
||||
BuildOptions() controllerapi.BuildOptions
|
||||
Inline() string
|
||||
ShouldExec() bool
|
||||
Secrets() session.Attachable
|
||||
}
|
||||
|
||||
var _ Client = (*cli)(nil)
|
||||
|
||||
func newDockerCLI(config *Config) (*command.DockerCli, error) {
|
||||
cli, err := command.NewDockerCli(
|
||||
command.WithDefaultContextStoreConfig(),
|
||||
command.WithContentTrustFromEnv(),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
opts := flags.NewClientOptions()
|
||||
if config != nil && config.Host != "" {
|
||||
opts.Hosts = append(opts.Hosts, config.Host)
|
||||
}
|
||||
err = cli.Initialize(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO: Log some version information for debugging.
|
||||
|
||||
return cli, nil
|
||||
}
|
||||
|
||||
// Build performs a BuildKit build. Returns a map of target names (or one name,
|
||||
// "default", if no targets were specified) to SolveResponses, which capture
|
||||
// the build's digest and tags (if any).
|
||||
func (c *cli) Build(
|
||||
pctx provider.Context,
|
||||
build Build,
|
||||
) (*client.SolveResponse, error) {
|
||||
ctx := context.Context(pctx)
|
||||
opts := build.BuildOptions()
|
||||
|
||||
go c.tail(pctx)
|
||||
defer contract.IgnoreClose(c)
|
||||
|
||||
if build.ShouldExec() {
|
||||
return c.execBuild(build)
|
||||
}
|
||||
|
||||
b, err := c.host.builderFor(build)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
printer, err := progress.NewPrinter(ctx, c.w,
|
||||
progressui.PlainMode,
|
||||
progress.WithDesc(
|
||||
fmt.Sprintf("building with %q instance using %s driver", b.name, b.driver),
|
||||
fmt.Sprintf("%s:%s", b.driver, b.name),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating printer: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
// Log any warnings when we're done.
|
||||
_ = printer.Wait()
|
||||
for _, w := range printer.Warnings() {
|
||||
b := &bytes.Buffer{}
|
||||
fmt.Fprint(b, w.Short)
|
||||
for _, d := range w.Detail {
|
||||
fmt.Fprintf(b, "\n%s", d)
|
||||
}
|
||||
pctx.Log(diag.Warning, b.String())
|
||||
}
|
||||
}()
|
||||
|
||||
cacheFrom := []client.CacheOptionsEntry{}
|
||||
for _, c := range opts.CacheFrom {
|
||||
if c == nil {
|
||||
continue
|
||||
}
|
||||
cacheFrom = append(cacheFrom, client.CacheOptionsEntry{
|
||||
Type: c.Type,
|
||||
Attrs: c.Attrs,
|
||||
})
|
||||
}
|
||||
cacheTo := []client.CacheOptionsEntry{}
|
||||
for _, c := range opts.CacheTo {
|
||||
if c == nil {
|
||||
continue
|
||||
}
|
||||
cacheTo = append(cacheTo, client.CacheOptionsEntry{
|
||||
Type: c.Type,
|
||||
Attrs: c.Attrs,
|
||||
})
|
||||
}
|
||||
exports := []client.ExportEntry{}
|
||||
for _, e := range opts.Exports {
|
||||
if e == nil {
|
||||
continue
|
||||
}
|
||||
exports = append(exports, client.ExportEntry{
|
||||
Type: e.Type,
|
||||
Attrs: e.Attrs,
|
||||
OutputDir: e.Destination,
|
||||
})
|
||||
}
|
||||
platforms, _ := platformutil.Parse(opts.Platforms)
|
||||
platforms = platformutil.Dedupe(platforms)
|
||||
|
||||
namedContexts := map[string]buildx.NamedContext{}
|
||||
for k, v := range opts.NamedContexts {
|
||||
ref, err := reference.ParseNormalizedNamed(k)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
name := strings.TrimSuffix(reference.FamiliarString(ref), ":latest")
|
||||
namedContexts[name] = buildx.NamedContext{Path: v}
|
||||
}
|
||||
|
||||
ssh, err := controllerapi.CreateSSH(opts.SSH)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
target := opts.Target
|
||||
if target == "" {
|
||||
target = "default"
|
||||
}
|
||||
payload := map[string]buildx.Options{
|
||||
target: {
|
||||
Inputs: buildx.Inputs{
|
||||
ContextPath: opts.ContextPath,
|
||||
DockerfilePath: opts.DockerfileName,
|
||||
DockerfileInline: build.Inline(),
|
||||
NamedContexts: namedContexts,
|
||||
InStream: strings.NewReader(""),
|
||||
},
|
||||
// Disable default provenance for now. Docker's `manifest create`
|
||||
// doesn't handle manifests with provenance included; more reason
|
||||
// to use imagetools instead.
|
||||
Attests: map[string]*string{"provenance": nil},
|
||||
BuildArgs: opts.BuildArgs,
|
||||
CacheFrom: cacheFrom,
|
||||
CacheTo: cacheTo,
|
||||
Exports: exports,
|
||||
ExtraHosts: opts.ExtraHosts,
|
||||
NetworkMode: opts.NetworkMode,
|
||||
NoCache: opts.NoCache,
|
||||
Labels: opts.Labels,
|
||||
Platforms: platforms,
|
||||
Pull: opts.Pull,
|
||||
Tags: opts.Tags,
|
||||
Target: opts.Target,
|
||||
|
||||
Session: []session.Attachable{
|
||||
ssh,
|
||||
authprovider.NewDockerAuthProvider(c.ConfigFile(), nil),
|
||||
build.Secrets(),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Perform the build.
|
||||
results, err := buildx.Build(
|
||||
ctx,
|
||||
b.nodes,
|
||||
payload,
|
||||
dockerutil.NewClient(c),
|
||||
filepath.Dir(c.ConfigFile().Filename),
|
||||
printer,
|
||||
)
|
||||
if err != nil {
|
||||
c.dumplogs = true
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return results[target], err
|
||||
}
|
||||
|
||||
// BuildKitEnabled returns true if the client supports buildkit.
|
||||
func (c *cli) BuildKitEnabled() (bool, error) {
|
||||
return c.Cli.BuildKitEnabled()
|
||||
}
|
||||
|
||||
func (c *cli) ManifestCreate(ctx provider.Context, push bool, target string, refs ...string) error {
|
||||
go c.tail(ctx)
|
||||
defer contract.IgnoreClose(c)
|
||||
|
||||
args := []string{
|
||||
// "buildx",
|
||||
"imagetools",
|
||||
"create",
|
||||
"--progress=plain",
|
||||
"--tag", target,
|
||||
}
|
||||
|
||||
if !push {
|
||||
args = append(args, "--dry-run")
|
||||
}
|
||||
|
||||
args = append(args, refs...)
|
||||
|
||||
cmd := commands.NewRootCmd(os.Args[0], false, c)
|
||||
|
||||
cmd.SetArgs(args)
|
||||
cmd.SetErr(c.Err())
|
||||
cmd.SetOut(c.Out())
|
||||
|
||||
ctx.Log(diag.Debug, fmt.Sprint("creating manifest with args", args))
|
||||
return cmd.ExecuteContext(ctx)
|
||||
}
|
||||
|
||||
func (c *cli) ManifestInspect(ctx provider.Context, target string) (string, error) {
|
||||
rc := c.rc()
|
||||
|
||||
ref, err := ref.New(target)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
m, err := rc.ManifestHead(ctx, ref)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("fetching %q: %w", ref, err)
|
||||
}
|
||||
|
||||
return string(m.GetDescriptor().Digest), nil
|
||||
}
|
||||
|
||||
func (c *cli) ManifestDelete(ctx provider.Context, target string) error {
|
||||
rc := c.rc()
|
||||
|
||||
ref, err := ref.New(target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = rc.ManifestDelete(context.Context(ctx), ref)
|
||||
if errors.Is(err, errs.ErrHTTPStatus) {
|
||||
ctx.Log(diag.Warning, "this registry does not support deletions")
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Inspect inspects an image.
|
||||
func (c *cli) Inspect(ctx context.Context, r string) ([]descriptor.Descriptor, error) {
|
||||
ref, err := ref.New(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rc := c.rc()
|
||||
|
||||
m, err := rc.ManifestGet(ctx, ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if mi, ok := m.(manifest.Indexer); ok {
|
||||
return mi.GetManifestList()
|
||||
}
|
||||
|
||||
return []descriptor.Descriptor{m.GetDescriptor()}, nil
|
||||
}
|
||||
|
||||
// Delete attempts to delete an image with the given ref. Many registries don't
|
||||
// support the DELETE API yet, so this operation is not guaranteed to work.
|
||||
func (c *cli) Delete(ctx context.Context, r string) error {
|
||||
// Attempt to delete the ref locally if it exists.
|
||||
_, _ = c.Client().ImageRemove(ctx, r, image.RemoveOptions{
|
||||
Force: true, // Needed in case the image has multiple tags.
|
||||
})
|
||||
|
||||
// Attempt to delete the ref remotely if it was pushed -- requires a
|
||||
// digest.
|
||||
ref, err := ref.New(r)
|
||||
if err != nil || ref.Digest == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
rc := c.rc()
|
||||
|
||||
// TODO: Multi-platform manifests are left dangling on ECR.
|
||||
|
||||
_ = rc.ManifestDelete(ctx, ref)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func normalizeReference(ref string) (reference.Named, error) {
|
||||
namedRef, err := reference.ParseNormalizedNamed(ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, isDigested := namedRef.(reference.Canonical); !isDigested {
|
||||
return reference.TagNameOnly(namedRef), nil
|
||||
}
|
||||
return namedRef, nil
|
||||
}
|
||||
481
provider/internal/client_test.go
Normal file
481
provider/internal/client_test.go
Normal file
@@ -0,0 +1,481 @@
|
||||
// Copyright 2024, 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 internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types/registry"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/mock/gomock"
|
||||
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/common/diag"
|
||||
)
|
||||
|
||||
func TestAuth(t *testing.T) {
|
||||
t.Parallel()
|
||||
user := "pulumibot"
|
||||
if u := os.Getenv("DOCKER_HUB_USER"); u != "" {
|
||||
user = u
|
||||
}
|
||||
password := os.Getenv("DOCKER_HUB_PASSWORD")
|
||||
address := "docker.io"
|
||||
|
||||
cli := testcli(t, true, Registry{
|
||||
Address: address,
|
||||
Username: user,
|
||||
Password: password,
|
||||
})
|
||||
|
||||
_, err := cli.Client().
|
||||
RegistryLogin(context.Background(), registry.AuthConfig{ServerAddress: address})
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestCustomHost(t *testing.T) {
|
||||
socket := "unix:///foo/bar.sock"
|
||||
|
||||
//nolint:paralleltest // not compatible with Setenv
|
||||
t.Run("env", func(t *testing.T) {
|
||||
t.Setenv("DOCKER_HOST", socket)
|
||||
|
||||
h, err := newHost(nil)
|
||||
require.NoError(t, err)
|
||||
cli, err := wrap(h)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, socket, cli.Client().DaemonHost())
|
||||
assert.Equal(t, socket, cli.DockerEndpoint().Host)
|
||||
})
|
||||
|
||||
t.Run("config", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
h, err := newHost(&Config{Host: socket})
|
||||
require.NoError(t, err)
|
||||
cli, err := wrap(h)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, socket, cli.Client().DaemonHost())
|
||||
assert.Equal(t, socket, cli.DockerEndpoint().Host)
|
||||
})
|
||||
}
|
||||
|
||||
func TestBuild(t *testing.T) {
|
||||
t.Parallel()
|
||||
// Workaround for https://github.com/pulumi/pulumi-go-provider/issues/159
|
||||
ctrl, ctx := gomock.WithContext(context.Background(), t)
|
||||
pctx := NewMockProviderContext(ctrl)
|
||||
pctx.EXPECT().Log(gomock.Any(), gomock.Any()).AnyTimes()
|
||||
pctx.EXPECT().LogStatus(gomock.Any(), gomock.Any()).AnyTimes()
|
||||
pctx.EXPECT().Done().Return(ctx.Done()).AnyTimes()
|
||||
pctx.EXPECT().
|
||||
Value(gomock.Any()).
|
||||
DoAndReturn(func(key any) any { return ctx.Value(key) }).
|
||||
AnyTimes()
|
||||
pctx.EXPECT().Err().Return(ctx.Err()).AnyTimes()
|
||||
pctx.EXPECT().Deadline().Return(ctx.Deadline()).AnyTimes()
|
||||
|
||||
tmpdir := t.TempDir()
|
||||
max := Max
|
||||
|
||||
exampleContext := &BuildContext{Context: Context{Location: "../../examples/app"}}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
skip bool
|
||||
args ImageArgs
|
||||
|
||||
auths []Registry
|
||||
}{
|
||||
{
|
||||
name: "multiPlatform",
|
||||
args: ImageArgs{
|
||||
Context: exampleContext,
|
||||
Dockerfile: &Dockerfile{
|
||||
Location: "../../examples/app/Dockerfile.multiPlatform",
|
||||
},
|
||||
Platforms: []Platform{"plan9/amd64", "plan9/arm64"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "registryPush",
|
||||
skip: os.Getenv("DOCKER_HUB_PASSWORD") == "",
|
||||
args: ImageArgs{
|
||||
Context: exampleContext,
|
||||
Tags: []string{"docker.io/pulumibot/buildkit-e2e:unit"},
|
||||
Push: true,
|
||||
},
|
||||
auths: []Registry{{
|
||||
Address: "docker.io",
|
||||
Username: "pulumibot",
|
||||
Password: os.Getenv("DOCKER_HUB_PASSWORD"),
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "cached",
|
||||
args: ImageArgs{
|
||||
Context: exampleContext,
|
||||
Tags: []string{"cached"},
|
||||
CacheTo: []CacheTo{{Local: &CacheToLocal{
|
||||
Dest: filepath.Join(tmpdir, "cache"),
|
||||
CacheWithMode: CacheWithMode{Mode: &max},
|
||||
}}},
|
||||
CacheFrom: []CacheFrom{{Local: &CacheFromLocal{
|
||||
Src: filepath.Join(tmpdir, "cache"),
|
||||
}}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "buildArgs",
|
||||
args: ImageArgs{
|
||||
Context: exampleContext,
|
||||
Dockerfile: &Dockerfile{
|
||||
Location: "../../examples/app/Dockerfile.buildArgs",
|
||||
},
|
||||
BuildArgs: map[string]string{
|
||||
"SET_ME_TO_TRUE": "true",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "extraHosts",
|
||||
args: ImageArgs{
|
||||
Context: exampleContext,
|
||||
Dockerfile: &Dockerfile{
|
||||
Location: "../../examples/app/Dockerfile.extraHosts",
|
||||
},
|
||||
AddHosts: []string{
|
||||
"metadata.google.internal:169.254.169.254",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sshMount",
|
||||
skip: os.Getenv("SSH_AUTH_SOCK") == "",
|
||||
args: ImageArgs{
|
||||
Context: exampleContext,
|
||||
Dockerfile: &Dockerfile{
|
||||
Location: "../../examples/app/Dockerfile.sshMount",
|
||||
},
|
||||
SSH: []SSH{{ID: "default"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "secrets",
|
||||
args: ImageArgs{
|
||||
Context: exampleContext,
|
||||
Dockerfile: &Dockerfile{
|
||||
Location: "../../examples/app/Dockerfile.secrets",
|
||||
},
|
||||
Secrets: map[string]string{
|
||||
"password": "hunter2",
|
||||
},
|
||||
NoCache: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "labels",
|
||||
args: ImageArgs{
|
||||
Context: exampleContext,
|
||||
Labels: map[string]string{
|
||||
"description": "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "target",
|
||||
args: ImageArgs{
|
||||
Context: exampleContext,
|
||||
Dockerfile: &Dockerfile{
|
||||
Location: "../../examples/app/Dockerfile.target",
|
||||
},
|
||||
Target: "build-me",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "namedContext",
|
||||
args: ImageArgs{
|
||||
Context: &BuildContext{
|
||||
Context: Context{
|
||||
Location: "../../examples/app",
|
||||
},
|
||||
Named: NamedContexts{
|
||||
"golang:latest": Context{
|
||||
Location: "docker-image://golang@sha256:b8e62cf593cdaff36efd90aa3a37de268e6781a2e68c6610940c48f7cdf36984",
|
||||
},
|
||||
},
|
||||
},
|
||||
Dockerfile: &Dockerfile{
|
||||
Location: "../../examples/app/Dockerfile.namedContexts",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "remoteContext",
|
||||
args: ImageArgs{
|
||||
Context: &BuildContext{
|
||||
Context: Context{
|
||||
Location: "https://raw.githubusercontent.com/pulumi/pulumi-docker/api-types/provider/testdata/Dockerfile",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "remoteContextWithInline",
|
||||
args: ImageArgs{
|
||||
Context: &BuildContext{
|
||||
Context: Context{
|
||||
Location: "https://github.com/docker-library/hello-world.git",
|
||||
},
|
||||
},
|
||||
Dockerfile: &Dockerfile{
|
||||
Inline: dedent(`
|
||||
FROM busybox
|
||||
COPY hello.c ./
|
||||
`),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "inline",
|
||||
args: ImageArgs{
|
||||
Context: exampleContext,
|
||||
Dockerfile: &Dockerfile{
|
||||
Inline: dedent(`
|
||||
FROM alpine
|
||||
RUN echo 👍
|
||||
`),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "dockerLoad",
|
||||
args: ImageArgs{
|
||||
Context: exampleContext,
|
||||
Load: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Add an exec: true version for all of our test cases.
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
tt.name = "exec-" + tt.name
|
||||
tt.args.Exec = true
|
||||
tmpdir := filepath.Join(t.TempDir(), "exec")
|
||||
for _, c := range tt.args.CacheTo {
|
||||
if c.Local != nil {
|
||||
c.Local.Dest = tmpdir
|
||||
}
|
||||
}
|
||||
for _, c := range tt.args.CacheFrom {
|
||||
if c.Local != nil {
|
||||
c.Local.Src = tmpdir
|
||||
}
|
||||
}
|
||||
tests = append(tests, tt)
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if tt.skip {
|
||||
t.Skip()
|
||||
}
|
||||
cli := testcli(t, true, tt.auths...)
|
||||
|
||||
build, err := tt.args.toBuild(pctx, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = cli.Build(pctx, build)
|
||||
assert.NoError(t, err, cli.err.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildkitEnabled(t *testing.T) {
|
||||
t.Parallel()
|
||||
cli := testcli(t, false)
|
||||
ok, err := cli.BuildKitEnabled()
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, ok)
|
||||
}
|
||||
|
||||
func TestInspect(t *testing.T) {
|
||||
t.Parallel()
|
||||
cli := testcli(t, false)
|
||||
descriptors, err := cli.Inspect(context.Background(), "pulumibot/myapp:buildx")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(
|
||||
t,
|
||||
"application/vnd.docker.distribution.manifest.v2+json",
|
||||
descriptors[0].MediaType,
|
||||
)
|
||||
}
|
||||
|
||||
func TestNormalizeReference(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
ref string
|
||||
want string
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
ref: "foo",
|
||||
want: "docker.io/library/foo:latest",
|
||||
},
|
||||
{
|
||||
ref: "pulumi/pulumi:v3.100.0",
|
||||
want: "docker.io/pulumi/pulumi:v3.100.0",
|
||||
},
|
||||
{
|
||||
ref: "invalid:ref:format",
|
||||
wantErr: "invalid reference format",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.ref, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ref, err := normalizeReference(tt.ref)
|
||||
if err != nil {
|
||||
assert.ErrorContains(t, err, tt.wantErr)
|
||||
} else {
|
||||
assert.Equal(t, ref.String(), tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildError(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if os.Getenv("CI") != "" {
|
||||
t.Skip("flaky on CI for some reason")
|
||||
}
|
||||
|
||||
ctrl, ctx := gomock.WithContext(context.Background(), t)
|
||||
|
||||
exampleContext := &BuildContext{Context: Context{Location: "../../examples/app"}}
|
||||
|
||||
args := ImageArgs{
|
||||
Context: exampleContext,
|
||||
Dockerfile: &Dockerfile{
|
||||
Inline: "FROM alpine\nRUN echo hello\nRUN badcmd",
|
||||
},
|
||||
}
|
||||
logged := bytes.Buffer{}
|
||||
|
||||
pctx := NewMockProviderContext(ctrl)
|
||||
pctx.EXPECT().Done().Return(ctx.Done()).AnyTimes()
|
||||
pctx.EXPECT().
|
||||
Value(gomock.Any()).
|
||||
DoAndReturn(func(key any) any { return ctx.Value(key) }).
|
||||
AnyTimes()
|
||||
pctx.EXPECT().Err().Return(ctx.Err()).AnyTimes()
|
||||
pctx.EXPECT().Deadline().Return(ctx.Deadline()).AnyTimes()
|
||||
|
||||
pctx.EXPECT().LogStatus(gomock.Any(), gomock.Any()).AnyTimes()
|
||||
pctx.EXPECT().Log(gomock.Any(), gomock.Any()).DoAndReturn(func(_ diag.Severity, msg string) {
|
||||
logged.WriteString(msg)
|
||||
}).AnyTimes()
|
||||
|
||||
cli := testcli(t, true)
|
||||
|
||||
build, err := args.toBuild(pctx, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = cli.Build(pctx, build)
|
||||
assert.Error(t, err)
|
||||
|
||||
want := []string{
|
||||
`RUN echo hello`,
|
||||
`/bin/sh: badcmd: not found`,
|
||||
}
|
||||
|
||||
for _, want := range want {
|
||||
assert.Contains(t, logged.String(), want)
|
||||
}
|
||||
assert.ErrorContains(t, err,
|
||||
`process "/bin/sh -c badcmd" did not complete successfully: exit code: 127`,
|
||||
)
|
||||
}
|
||||
|
||||
func TestBuildExecError(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctrl, _ := gomock.WithContext(context.Background(), t)
|
||||
|
||||
exampleContext := &BuildContext{Context: Context{Location: "../../examples/app"}}
|
||||
|
||||
args := ImageArgs{
|
||||
Context: exampleContext,
|
||||
Dockerfile: &Dockerfile{
|
||||
Inline: "FROM alpine\nRUN echo hello\nRUN badcmd",
|
||||
},
|
||||
Exec: true,
|
||||
}
|
||||
|
||||
pctx := NewMockProviderContext(ctrl)
|
||||
pctx.EXPECT().Log(
|
||||
diag.Warning,
|
||||
"No exports were specified so the build will only remain in the local build cache. "+
|
||||
"Use `push` to upload the image to a registry, or silence this warning with a `cacheonly` export.",
|
||||
)
|
||||
pctx.EXPECT().LogStatus(gomock.Any(), gomock.Any()).AnyTimes()
|
||||
|
||||
cli := testcli(t, true)
|
||||
|
||||
build, err := args.toBuild(pctx, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = cli.Build(pctx, build)
|
||||
assert.Error(t, err)
|
||||
|
||||
want := []string{
|
||||
`RUN echo hello`,
|
||||
`/bin/sh: badcmd: not found`,
|
||||
`process "/bin/sh -c badcmd" did not complete successfully: exit code: 127`,
|
||||
}
|
||||
|
||||
for _, want := range want {
|
||||
assert.Contains(t, cli.err.String(), want)
|
||||
}
|
||||
}
|
||||
|
||||
// testcli returns a new standalone CLI instance. Set ping to true if a live
|
||||
// daemon is required -- the test will be skipped if the daemon is not available.
|
||||
func testcli(t *testing.T, ping bool, auths ...Registry) *cli {
|
||||
h, err := newHost(nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
cli, err := wrap(h, auths...)
|
||||
require.NoError(t, err)
|
||||
|
||||
if ping {
|
||||
_, err := cli.Client().Ping(context.Background())
|
||||
if err != nil {
|
||||
t.Skip(err)
|
||||
}
|
||||
}
|
||||
|
||||
return cli
|
||||
}
|
||||
345
provider/internal/context.go
Normal file
345
provider/internal/context.go
Normal file
@@ -0,0 +1,345 @@
|
||||
// Copyright 2024, 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 internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
gofs "io/fs"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
buildx "github.com/docker/buildx/build"
|
||||
"github.com/moby/patternmatcher/ignorefile"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/tonistiigi/fsutil"
|
||||
|
||||
"github.com/pulumi/pulumi-go-provider/infer"
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
|
||||
)
|
||||
|
||||
var (
|
||||
_ infer.Annotated = (*Context)(nil)
|
||||
_ infer.Annotated = (*BuildContext)(nil)
|
||||
)
|
||||
|
||||
// Context represents Docker's `PATH | URL | -` context argument. Inline
|
||||
// context isn't supported yet.
|
||||
type Context struct {
|
||||
Location string `pulumi:"location"` // Location is a local directory or URL.
|
||||
}
|
||||
|
||||
// BuildContext represents Docker's named and unamed contexts.
|
||||
type BuildContext struct {
|
||||
Context
|
||||
Named NamedContexts `pulumi:"named,optional"`
|
||||
}
|
||||
|
||||
func (bc *BuildContext) namedMap() map[string]string {
|
||||
if bc == nil {
|
||||
return nil
|
||||
}
|
||||
return bc.Named.Map()
|
||||
}
|
||||
|
||||
// NamedContexts correspond to Docker's `--build-context name=path` options.
|
||||
// The path can be local or a remote URL.
|
||||
type NamedContexts map[string]Context
|
||||
|
||||
// Map returns NamedContexts as a simple map.
|
||||
func (nc NamedContexts) Map() map[string]string {
|
||||
m := map[string]string{}
|
||||
for k, v := range nc {
|
||||
m[k] = v.Location
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// Annotate sets docstrings on Context.
|
||||
func (c *Context) Annotate(a infer.Annotator) {
|
||||
a.Describe(&c.Location, dedent(`
|
||||
Resources to use for build context.
|
||||
|
||||
The location can be:
|
||||
* A relative or absolute path to a local directory (".", "./app",
|
||||
"/app", etc.).
|
||||
* A remote URL of a Git repository, tarball, or plain text file
|
||||
("https://github.com/user/myrepo.git", "http://server/context.tar.gz",
|
||||
etc.).
|
||||
`))
|
||||
}
|
||||
|
||||
// validate returns a non-nil CheckError if the Context is invalid. The
|
||||
// returned Dockerfile may have defaults set to match Docker's default
|
||||
// handling. The returned Dockerfile should be validated separately.
|
||||
func (bc *BuildContext) validate(preview bool, d *Dockerfile) (*Dockerfile, *Context, error) {
|
||||
if d == nil {
|
||||
d = &Dockerfile{}
|
||||
}
|
||||
c := &Context{}
|
||||
if bc != nil {
|
||||
c = &bc.Context
|
||||
}
|
||||
|
||||
if c.Location == "" && preview {
|
||||
// The field is required so we normally wouldn't need to check if it
|
||||
// exists, but during previews it can still be empty if the value is
|
||||
// unknown. This isn't an error, but it does prevent us from performing
|
||||
// a build later.
|
||||
return d, c, nil
|
||||
}
|
||||
|
||||
if buildx.IsRemoteURL(c.Location) {
|
||||
// We assume remote URLs are always valid.
|
||||
return d, c, nil
|
||||
}
|
||||
|
||||
abs, err := filepath.Abs(c.Location)
|
||||
if err != nil {
|
||||
return d, c, newCheckFailure(err, "context.location")
|
||||
}
|
||||
|
||||
if d.Location == "" && d.Inline == "" {
|
||||
// If a Dockerfile wasn't provided and our context is on-disk, then
|
||||
// set our Dockerfile to a default of <PATH>/Dockerfile.
|
||||
d.Location = filepath.Join(c.Location, "Dockerfile")
|
||||
}
|
||||
|
||||
if isLocalDir(afero.NewOsFs(), abs) {
|
||||
// Our context exists -- nothing else to check.
|
||||
return d, c, nil
|
||||
}
|
||||
|
||||
if c.Location != "-" {
|
||||
return d, c, newCheckFailure(
|
||||
fmt.Errorf("%q: not a valid directory or URL", c.Location),
|
||||
"context.location",
|
||||
)
|
||||
}
|
||||
|
||||
return d, c, nil
|
||||
}
|
||||
|
||||
// Annotate sets docstrings on BuildContext.
|
||||
func (bc *BuildContext) Annotate(a infer.Annotator) {
|
||||
a.Describe(&bc.Named, dedent(`
|
||||
Additional build contexts to use.
|
||||
|
||||
These contexts are accessed with "FROM name" or "--from=name"
|
||||
statements when using Dockerfile 1.4+ syntax.
|
||||
|
||||
Values can be local paths, HTTP URLs, or "docker-image://" images.
|
||||
`))
|
||||
}
|
||||
|
||||
// hashFile hashes a file's contents and accumulates it into the provider Hash.
|
||||
func hashFile(
|
||||
h hash.Hash,
|
||||
fs fsutil.FS,
|
||||
relativePath string,
|
||||
fileMode gofs.FileMode,
|
||||
) error {
|
||||
if fileMode.IsDir() {
|
||||
return nil
|
||||
}
|
||||
if !(fileMode.IsRegular() || fileMode.Type() == os.ModeSymlink) {
|
||||
return nil
|
||||
}
|
||||
|
||||
f, err := fs.Open(relativePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not open %q: %w", relativePath, err)
|
||||
}
|
||||
defer contract.IgnoreClose(f)
|
||||
|
||||
_, err = io.Copy(h, f)
|
||||
if errors.Is(err, syscall.EISDIR) {
|
||||
// Ignore symlinks to directories.
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not copy %q to hash: %w", relativePath, err)
|
||||
}
|
||||
|
||||
h.Write([]byte(filepath.ToSlash(path.Clean(relativePath))))
|
||||
h.Write([]byte(fileMode.String()))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// hashBuildContext accumulates hashes for files in a directory. If the file is
|
||||
// a symlink, the location it points to is hashed. If it is a regular file, we
|
||||
// hash the contents of the file. In order to detect file renames and mode
|
||||
// changes, we also write to the accumulator a relative name and file mode.
|
||||
func hashBuildContext(
|
||||
contextPath, dockerfilePath string,
|
||||
namedContexts map[string]string,
|
||||
) (string, error) {
|
||||
h := sha256.New()
|
||||
fs := afero.NewOsFs()
|
||||
|
||||
// Grab .dockerignore if our context and/or Dockerfile is on-disk.
|
||||
excludes := []string{}
|
||||
if isLocalDir(fs, contextPath) || isLocalFile(fs, dockerfilePath) {
|
||||
e, err := getIgnorePatterns(fs, dockerfilePath, contextPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
excludes = e
|
||||
}
|
||||
|
||||
if isLocalFile(fs, dockerfilePath) {
|
||||
err := hashDockerfile(h, dockerfilePath)
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
|
||||
if isLocalDir(fs, contextPath) {
|
||||
// Hash our context if it's on-disk.
|
||||
fs, err := rootFS(contextPath, excludes)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if _, err := hashPath(h, fs); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
// Hash any local named contexts.
|
||||
for _, namedContext := range namedContexts {
|
||||
if isLocalDir(fs, namedContext) {
|
||||
fs, err := rootFS(namedContext, excludes)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if _, err := hashPath(h, fs); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return hex.EncodeToString(h.Sum(nil)), nil
|
||||
}
|
||||
|
||||
// hashPath hashes all paths within the provided FS.
|
||||
func hashPath(h hash.Hash, fs fsutil.FS) (string, error) {
|
||||
err := fs.Walk(
|
||||
context.Background(),
|
||||
"/",
|
||||
func(filePath string, dir gofs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if dir.IsDir() {
|
||||
return nil
|
||||
}
|
||||
// fsutil.Walk makes filePath relative to the root, we join it back to get an absolute path to
|
||||
// the file to hash.
|
||||
fi, err := dir.Info()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return hashFile(h, fs, filePath, fi.Mode())
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unable to hash build context: %w", err)
|
||||
}
|
||||
// create a hash of the entire input of the hash accumulator
|
||||
return hex.EncodeToString(h.Sum(nil)), nil
|
||||
}
|
||||
|
||||
// hashDockerfile hashes the contents of a Dockerfile.
|
||||
func hashDockerfile(h hash.Hash, path string) error {
|
||||
// The Dockerfile might be capture by .dockerignore, so we explicitly hash
|
||||
// its content (but not filename -- to match Docker) in order to detect
|
||||
// changes in it.
|
||||
df, err := os.ReadFile(filepath.Clean(path))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading dockerfile %q: %w", path, err)
|
||||
}
|
||||
_, err = h.Write(df)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error hashing dockerfile %q: %w", path, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getIgnorePatterns returns all patterns to ignore when constructing a build
|
||||
// context for the given Dockerfile, if any such patterns exist.
|
||||
//
|
||||
// Precedence is given to Dockerfile-specific ignore-files as per
|
||||
// https://docs.docker.com/build/building/context/#filename-and-location.
|
||||
func getIgnorePatterns(fs afero.Fs, dockerfilePath, contextRoot string) ([]string, error) {
|
||||
paths := []string{
|
||||
// Prefer <Dockerfile>.dockerignore if it's present.
|
||||
dockerfilePath + ".dockerignore",
|
||||
}
|
||||
|
||||
if isLocalDir(fs, contextRoot) {
|
||||
// Otherwise fall back to the ignore-file at the root of our build context.
|
||||
paths = append(paths, filepath.Join(contextRoot, ".dockerignore"))
|
||||
}
|
||||
|
||||
// Attempt to parse our candidate ignore-files, skipping any that don't
|
||||
// exist.
|
||||
for _, p := range paths {
|
||||
f, err := fs.Open(p)
|
||||
if errors.Is(err, afero.ErrFileNotFound) {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading %q: %w", p, err)
|
||||
}
|
||||
|
||||
ignorePatterns, err := ignorefile.ReadAll(f)
|
||||
if err != nil {
|
||||
contract.IgnoreClose(f)
|
||||
return nil, fmt.Errorf("unable to parse %q: %w", p, err)
|
||||
}
|
||||
contract.IgnoreClose(f)
|
||||
return ignorePatterns, nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func isLocalDir(fs afero.Fs, path string) bool {
|
||||
stat, err := fs.Stat(path)
|
||||
return err == nil && stat.IsDir()
|
||||
}
|
||||
|
||||
func isLocalFile(fs afero.Fs, path string) bool {
|
||||
stat, err := fs.Stat(path)
|
||||
return err == nil && !stat.IsDir()
|
||||
}
|
||||
|
||||
// rootFS returns a new fsutil.FS scoped to the given root and with the given
|
||||
// exclusions.
|
||||
func rootFS(root string, excludes []string) (fsutil.FS, error) {
|
||||
fs, err := fsutil.NewFS(root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fsutil.NewFilterFS(fs, &fsutil.FilterOpt{ExcludePatterns: excludes})
|
||||
}
|
||||
417
provider/internal/context_test.go
Normal file
417
provider/internal/context_test.go
Normal file
@@ -0,0 +1,417 @@
|
||||
// Copyright 2024, 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 internal
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/afero"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var _dockerfile = "Dockerfile"
|
||||
|
||||
func TestValidateContext(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
name string
|
||||
c Context
|
||||
givenD Dockerfile
|
||||
preview bool
|
||||
|
||||
wantD *Dockerfile
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "relative",
|
||||
c: Context{
|
||||
Location: "../internal/../internal/testdata/noop",
|
||||
},
|
||||
wantD: &Dockerfile{
|
||||
Location: "../internal/testdata/noop/Dockerfile",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "missing directory",
|
||||
c: Context{
|
||||
Location: "/does/not/exist/",
|
||||
},
|
||||
wantErr: "not a valid directory",
|
||||
},
|
||||
{
|
||||
name: "missing default Dockerfile",
|
||||
c: Context{
|
||||
Location: "testdata",
|
||||
},
|
||||
wantD: &Dockerfile{Location: "testdata/Dockerfile"},
|
||||
},
|
||||
{
|
||||
name: "with explicit Dockerfile",
|
||||
c: Context{
|
||||
Location: "testdata",
|
||||
},
|
||||
givenD: Dockerfile{
|
||||
Location: "testdata/Dockerfile.invalid",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "default location",
|
||||
c: Context{},
|
||||
wantD: &Dockerfile{Location: "Dockerfile"},
|
||||
},
|
||||
{
|
||||
name: "remote context doesn't default to local Dockerfile",
|
||||
c: Context{
|
||||
Location: "https://raw.githubusercontent.com/pulumi/pulumi-docker/api-types/provider/testdata/Dockerfile",
|
||||
},
|
||||
wantD: &Dockerfile{},
|
||||
},
|
||||
{
|
||||
name: "preview",
|
||||
c: Context{},
|
||||
preview: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
bc := &BuildContext{Context: tt.c}
|
||||
d, _, err := bc.validate(tt.preview, &tt.givenD)
|
||||
|
||||
if tt.wantErr == "" {
|
||||
assert.NoError(t, err)
|
||||
} else {
|
||||
assert.ErrorContains(t, err, tt.wantErr)
|
||||
}
|
||||
|
||||
if tt.wantD != nil {
|
||||
assert.Equal(t, tt.wantD.Location, d.Location)
|
||||
assert.Equal(t, tt.wantD.Inline, d.Inline)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHashIgnoresFile(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
step1Dir := "./testdata/ignores/basedir"
|
||||
baseResult, err := hashBuildContext(step1Dir, filepath.Join(step1Dir, _dockerfile), nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
step2Dir := "./testdata/ignores/basedir-with-ignored-files"
|
||||
result, err := hashBuildContext(step2Dir, filepath.Join(step2Dir, _dockerfile), nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, result, baseResult)
|
||||
}
|
||||
|
||||
// Tests that we handle .dockerignore exclusions such as "!foo/*/bar".
|
||||
//
|
||||
// See:
|
||||
// - https://github.com/moby/moby/issues/30018
|
||||
// - https://github.com/moby/moby/issues/45608
|
||||
//
|
||||
// Buildkit handles these correctly (according to spec), Docker's classic builder does not.
|
||||
func TestHashIgnoresWildcards(t *testing.T) {
|
||||
t.Parallel()
|
||||
baselineDir := "testdata/ignores-wildcard/basedir"
|
||||
baselineResult, err := hashBuildContext(
|
||||
baselineDir,
|
||||
filepath.Join(baselineDir, _dockerfile),
|
||||
nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
modIgnoredDir := "testdata/ignores-wildcard/basedir-modified-ignored-file"
|
||||
modIgnoredResult, err := hashBuildContext(
|
||||
modIgnoredDir,
|
||||
filepath.Join(modIgnoredDir, _dockerfile),
|
||||
nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
modIncludedDir := "testdata/ignores-wildcard/basedir-modified-included-file"
|
||||
modIncludedResult, err := hashBuildContext(
|
||||
modIncludedDir,
|
||||
filepath.Join(modIncludedDir, _dockerfile),
|
||||
nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(
|
||||
t,
|
||||
baselineResult,
|
||||
modIgnoredResult,
|
||||
"hash should not change when modifying ignored files",
|
||||
)
|
||||
assert.NotEqual(t, baselineResult, modIncludedResult,
|
||||
"hash should change when modifying included (via wildcard ignore exclusion) files")
|
||||
}
|
||||
|
||||
func BenchmarkHashBuildContext(b *testing.B) {
|
||||
dir := "testdata/ignores-wildcard/basedir-modified-ignored-file"
|
||||
for n := 0; n < b.N; n++ {
|
||||
_, err := hashBuildContext(dir, filepath.Join(dir, _dockerfile), nil)
|
||||
require.NoError(b, err)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that we handle .dockerignore exclusions such as "!foo/*/bar", as above, when using a
|
||||
// relative context path.
|
||||
//
|
||||
//nolint:paralleltest // Incompatible with os.Chdir.
|
||||
func TestHashIgnoresWildcardsRelative(t *testing.T) {
|
||||
err := os.Chdir("testdata")
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
err = os.Chdir("..")
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
|
||||
baselineDir := "../testdata/ignores-wildcard/basedir"
|
||||
baselineResult, err := hashBuildContext(
|
||||
baselineDir,
|
||||
filepath.Join(baselineDir, _dockerfile),
|
||||
nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
modIgnoredDir := "../testdata/ignores-wildcard/basedir-modified-ignored-file"
|
||||
modIgnoredResult, err := hashBuildContext(
|
||||
modIgnoredDir,
|
||||
filepath.Join(modIgnoredDir, _dockerfile),
|
||||
nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
modIncludedDir := "../testdata/ignores-wildcard/basedir-modified-included-file"
|
||||
modIncludedResult, err := hashBuildContext(
|
||||
modIncludedDir,
|
||||
filepath.Join(modIncludedDir, _dockerfile),
|
||||
nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(
|
||||
t,
|
||||
baselineResult,
|
||||
modIgnoredResult,
|
||||
"hash should not change when modifying ignored files",
|
||||
)
|
||||
assert.NotEqual(t, baselineResult, modIncludedResult,
|
||||
"hash should change when modifying included (via wildcard ignore exclusion) files")
|
||||
}
|
||||
|
||||
func TestHashIgnoresDockerfileOutsideDirMove(t *testing.T) {
|
||||
t.Parallel()
|
||||
appDir := "./testdata/dockerfile-location-irrelevant/app"
|
||||
baseResult, err := hashBuildContext(
|
||||
appDir,
|
||||
"./testdata/dockerfile-location-irrelevant/step1.Dockerfile",
|
||||
nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
result, err := hashBuildContext(
|
||||
appDir,
|
||||
"./testdata/dockerfile-location-irrelevant/step2.Dockerfile",
|
||||
nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, result, baseResult)
|
||||
}
|
||||
|
||||
func TestHashRenamingMatters(t *testing.T) {
|
||||
t.Parallel()
|
||||
step1Dir := "./testdata/filemode-matters/step1"
|
||||
baseResult, err := hashBuildContext(step1Dir, filepath.Join(step1Dir, _dockerfile), nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
step2Dir := "./testdata/renaming-matters/step2"
|
||||
result, err := hashBuildContext(step2Dir, filepath.Join(step2Dir, _dockerfile), nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.NotEqual(t, result, baseResult)
|
||||
}
|
||||
|
||||
func TestHashFilemodeMatters(t *testing.T) {
|
||||
t.Parallel()
|
||||
step1Dir := "./testdata/filemode-matters/step1"
|
||||
baseResult, err := hashBuildContext(step1Dir, filepath.Join(step1Dir, _dockerfile), nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
step2Dir := "./testdata/filemode-matters/step2-chmod-x"
|
||||
result, err := hashBuildContext(step2Dir, filepath.Join(step2Dir, _dockerfile), nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.NotEqual(t, result, baseResult)
|
||||
}
|
||||
|
||||
func TestHashDeepSymlinks(t *testing.T) {
|
||||
t.Parallel()
|
||||
dir := "./testdata/symlinks"
|
||||
_, err := hashBuildContext(dir, filepath.Join(dir, "Dockerfile"), nil)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestIgnoreIrregularFiles(t *testing.T) {
|
||||
t.Parallel()
|
||||
dir := t.TempDir()
|
||||
|
||||
// Create a Dockerfile
|
||||
dockerfile := filepath.Join(dir, "Dockerfile")
|
||||
err := os.WriteFile(dockerfile, []byte{}, 0o600)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create a pipe which should be ignored. (We will time out trying to read
|
||||
// it if it's not.)
|
||||
pipe := filepath.Join(dir, "pipe")
|
||||
err = syscall.Mkfifo(pipe, 0o666)
|
||||
require.NoError(t, err)
|
||||
// Confirm it's irregular.
|
||||
fi, err := os.Stat(pipe)
|
||||
require.NoError(t, err)
|
||||
assert.False(t, fi.Mode().IsRegular())
|
||||
|
||||
_, err = hashBuildContext(dir, dockerfile, nil)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestHashUnignoredDirs(t *testing.T) {
|
||||
t.Parallel()
|
||||
step1Dir := "./testdata/unignores/basedir"
|
||||
baseResult, err := hashBuildContext(step1Dir, filepath.Join(step1Dir, _dockerfile), nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
step2Dir := "./testdata/unignores/basedir-with-unignored-files"
|
||||
unignoreResult, err := hashBuildContext(step2Dir, filepath.Join(step2Dir, _dockerfile), nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, baseResult, unignoreResult)
|
||||
}
|
||||
|
||||
func TestDockerIgnore(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
dockerfile string
|
||||
context string
|
||||
fs map[string]string
|
||||
|
||||
want []string
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "Dockerfile with root dockerignore",
|
||||
dockerfile: "./foo/Dockerfile",
|
||||
fs: map[string]string{
|
||||
".dockerignore": "rootignore",
|
||||
},
|
||||
want: []string{"rootignore"},
|
||||
},
|
||||
{
|
||||
name: "Dockerfile with root dockerignore and custom dockerignore",
|
||||
dockerfile: "./foo/Dockerfile",
|
||||
fs: map[string]string{
|
||||
"foo/Dockerfile.dockerignore": "customignore",
|
||||
".dockerignore": "rootignore",
|
||||
},
|
||||
want: []string{"customignore"},
|
||||
},
|
||||
{
|
||||
name: "Dockerfile with root dockerignore and relative context",
|
||||
dockerfile: "./foo/Dockerfile",
|
||||
context: "../",
|
||||
fs: map[string]string{
|
||||
"../.dockerignore": "rootignore",
|
||||
},
|
||||
want: []string{"rootignore"},
|
||||
},
|
||||
{
|
||||
name: "Dockerfile without root dockerignore",
|
||||
dockerfile: "./foo/Dockerfile",
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
name: "Dockerfile with invalid root dockerignore",
|
||||
dockerfile: "./foo/Dockerfile",
|
||||
fs: map[string]string{
|
||||
".dockerignore": strings.Repeat("*", bufio.MaxScanTokenSize),
|
||||
},
|
||||
wantErr: bufio.ErrTooLong,
|
||||
},
|
||||
{
|
||||
name: "custom.Dockerfile without custom dockerignore and without root dockerignore",
|
||||
dockerfile: "./foo/custom.Dockerfile",
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
name: "custom.Dockerfile with custom dockerignore and without root dockerignore",
|
||||
dockerfile: "./foo/custom.Dockerfile",
|
||||
fs: map[string]string{
|
||||
"foo/custom.Dockerfile.dockerignore": "customignore",
|
||||
},
|
||||
want: []string{"customignore"},
|
||||
},
|
||||
{
|
||||
name: "custom.Dockerfile with custom dockerignore and with root dockerignore",
|
||||
dockerfile: "foo/custom.Dockerfile",
|
||||
fs: map[string]string{
|
||||
"foo/custom.Dockerfile.dockerignore": "customignore",
|
||||
".dockerignore": "rootignore",
|
||||
},
|
||||
want: []string{"customignore"},
|
||||
},
|
||||
{
|
||||
name: "custom.Dockerfile without custom dockerignore and with root dockerignore",
|
||||
dockerfile: "foo/custom.Dockerfile",
|
||||
fs: map[string]string{
|
||||
".dockerignore": "rootignore",
|
||||
},
|
||||
want: []string{"rootignore"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
fs := afero.NewMemMapFs()
|
||||
for fname, fdata := range tt.fs {
|
||||
f, err := fs.Create(fname)
|
||||
require.NoError(t, err)
|
||||
_, err = f.WriteString(fdata)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
actual, err := getIgnorePatterns(fs, tt.dockerfile, tt.context)
|
||||
|
||||
assert.ErrorIs(t, err, tt.wantErr)
|
||||
assert.Equal(t, tt.want, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
870
provider/internal/coverage.out
Normal file
870
provider/internal/coverage.out
Normal file
@@ -0,0 +1,870 @@
|
||||
mode: set
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:34.47,36.2 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:38.65,40.9 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:40.9,42.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:43.2,44.9 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:44.9,46.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:47.2,48.9 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:48.9,50.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:51.2,51.46 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:51.46,53.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:54.2,55.18 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:60.3,62.27 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:62.27,64.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:68.2,68.13 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:68.13,70.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:72.2,73.62 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:73.62,75.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:77.2,81.59 3 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:81.59,83.16 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:83.16,85.4 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:87.3,88.23 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:88.23,90.4 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:92.3,92.17 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:95.2,95.66 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:98.69,99.13 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:100.17,101.42 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:102.27,103.38 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:104.15,105.52 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:106.10,107.61 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:111.66,118.2 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:123.36,127.16 3 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:127.16,129.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:131.2,134.9 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:134.9,136.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:138.2,141.19 3 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:141.19,144.3 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:146.2,146.78 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:146.78,150.3 3 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:152.2,152.24 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:152.24,154.17 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:154.17,156.4 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:157.3,157.23 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:157.23,160.4 2 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:161.3,161.17 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:166.2,166.21 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:166.21,168.46 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:168.46,171.4 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:174.2,174.16 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:178.102,185.18 4 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:185.18,186.31 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:186.31,188.4 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:189.3,189.21 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:193.2,193.27 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:193.27,196.17 3 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:196.17,198.4 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:198.9,198.22 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:198.22,199.36 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:199.36,200.13 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:202.4,202.67 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:202.67,203.13 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:205.4,205.19 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/deprecated/configencoding.go:209.2,209.20 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/auth.go:11.53,15.2 3 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/builder.go:13.53,22.2 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/buildx.go:33.46,36.2 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/buildx.go:39.54,46.16 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/buildx.go:46.16,48.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/buildx.go:49.2,56.12 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/buildx.go:60.44,76.2 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/buildx.go:79.69,84.2 4 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:39.42,40.14 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:40.14,42.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:43.2,44.17 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:44.17,46.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:47.2,47.20 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:47.20,49.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:50.2,50.33 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:53.54,56.2 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:62.57,64.2 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:66.45,67.14 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:67.14,69.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:70.2,70.51 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:78.52,92.2 4 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:94.39,95.18 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:95.18,97.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:98.2,99.28 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:99.28,101.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:102.2,102.33 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:111.62,138.2 6 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:140.50,141.14 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:141.14,143.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:144.2,145.19 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:145.19,147.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:148.2,148.19 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:148.19,150.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:151.2,151.17 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:151.17,153.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:154.2,154.33 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:163.46,164.14 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:164.14,166.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:167.2,168.18 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:168.18,170.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:171.2,171.24 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:171.24,173.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:174.2,174.29 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:174.29,176.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:177.2,177.33 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:180.58,184.2 3 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:193.44,194.14 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:194.14,196.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:197.2,197.77 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:213.51,249.2 14 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:251.39,252.14 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:252.14,254.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:255.2,256.20 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:256.20,258.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:259.2,259.18 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:259.18,261.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:262.2,262.25 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:262.25,264.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:265.2,265.25 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:265.25,267.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:268.2,268.29 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:268.29,270.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:271.2,271.27 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:271.27,273.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:274.2,274.25 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:274.25,276.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:277.2,277.29 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:277.29,279.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:280.2,280.26 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:280.26,282.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:284.2,284.33 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:291.53,296.2 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:298.40,299.18 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:299.18,301.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:302.2,302.39 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:309.60,312.2 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:314.47,315.26 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:315.26,317.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:318.2,318.55 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:328.37,329.14 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:329.14,331.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:332.2,332.70 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:337.30,339.2 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:352.54,380.2 7 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:382.41,383.16 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:383.16,385.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:386.2,386.64 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:391.41,392.14 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:392.14,394.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:395.2,395.22 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:406.52,410.2 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:412.40,413.14 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:413.14,415.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:416.2,420.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:432.43,433.14 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:433.14,435.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:436.2,442.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:451.60,459.2 6 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:461.47,462.29 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:462.29,464.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:465.2,466.25 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:466.25,468.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:469.2,469.28 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:469.28,471.14 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:471.14,473.4 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:474.3,474.65 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:476.2,476.31 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:476.31,478.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:479.2,479.33 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:489.48,490.14 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:490.14,492.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:493.2,493.81 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:508.52,541.2 8 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:543.39,544.16 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:544.16,546.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:547.2,547.74 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:557.56,568.2 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:578.68,584.2 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:588.49,590.23 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:590.23,592.14 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:592.14,593.12 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:595.3,595.27 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:597.2,597.35 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cache.go:600.38,602.2 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:31.62,33.16 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:33.16,35.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:55.2,56.28 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:56.28,58.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:60.2,60.31 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:60.31,67.3 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:80.2,100.16 6 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:100.16,102.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:104.2,114.21 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:177.32,179.2 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:181.34,183.2 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:185.31,187.2 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:190.41,192.28 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:192.28,197.3 4 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:198.2,200.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:205.42,207.15 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:207.15,209.35 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:209.35,211.4 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:214.2,215.15 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:215.15,217.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:218.2,218.30 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:222.29,225.19 3 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:225.19,227.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:228.2,228.12 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:231.76,235.16 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:235.16,237.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:238.2,243.16 4 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:243.16,245.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:251.2,252.52 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:252.52,257.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:260.2,263.16 4 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:263.16,265.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:266.2,298.35 4 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:298.35,300.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:301.2,301.24 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:301.24,303.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:304.2,304.35 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:304.35,306.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:307.2,307.33 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:307.33,309.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:310.2,310.21 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:310.21,312.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:313.2,313.21 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:313.21,315.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:316.2,316.33 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:316.33,318.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:319.2,319.36 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:319.36,321.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:322.2,322.39 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:322.39,324.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:325.2,325.32 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:325.32,327.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:328.2,328.18 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:328.18,330.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:331.2,331.35 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:331.35,333.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:334.2,334.15 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:334.15,336.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:337.2,337.31 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:337.31,339.25 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:339.25,341.4 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:342.3,342.34 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:344.2,344.30 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:344.30,346.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:347.2,347.23 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:347.23,349.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:350.2,350.31 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:350.31,352.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:353.2,353.32 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:353.32,356.3 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:357.2,357.28 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:357.28,359.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:361.2,361.33 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:361.33,367.3 3 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:369.2,370.16 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:370.16,372.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:376.2,377.16 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:377.16,379.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:380.2,381.16 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:381.16,383.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:384.2,386.16 3 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:386.16,388.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:389.2,390.24 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:390.24,391.25 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:392.15,393.16 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:394.11,396.18 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:396.18,397.13 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:399.4,399.25 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:403.2,404.18 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:404.18,406.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:407.2,409.8 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:414.60,417.20 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:417.20,419.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:420.2,425.16 4 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:425.16,427.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:428.2,428.21 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:428.21,430.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:432.2,445.16 3 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:445.16,447.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:448.2,455.18 6 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:467.54,469.22 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:469.22,471.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/cli.go:472.2,472.29 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:71.63,101.16 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:101.16,103.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:105.2,106.40 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:106.40,108.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:109.2,111.16 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:111.16,113.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:118.2,120.17 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:193.45,212.24 5 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:212.24,214.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:231.2,232.16 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:232.16,234.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:235.2,242.16 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:242.16,244.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:246.2,247.35 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:247.35,252.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:253.2,254.33 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:254.33,259.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:260.2,261.33 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:261.33,267.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:268.2,280.39 4 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:280.39,282.17 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:282.17,284.4 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:285.3,286.53 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:289.2,290.16 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:290.16,292.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:294.2,295.18 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:295.18,297.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:298.2,339.16 3 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:339.16,341.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:343.2,343.49 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:343.49,345.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:346.2,346.39 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:346.39,349.30 3 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:349.30,351.4 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:352.3,352.37 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:355.2,355.21 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:359.47,361.2 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:363.100,441.16 10 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:441.16,443.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:445.2,445.11 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:445.11,447.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:449.2,453.16 5 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:453.16,455.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:456.2,456.12 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:459.84,472.16 3 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:472.16,474.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:476.2,477.16 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:477.16,479.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:481.2,481.46 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:547.73,561.56 8 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:561.56,563.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:567.2,567.12 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:571.94,573.16 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:573.16,575.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:578.2,579.85 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:579.85,581.21 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:581.21,583.4 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:584.3,584.49 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:597.2,603.80 3 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:603.80,607.3 3 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:609.2,609.23 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:613.86,617.2 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:619.62,621.16 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:621.16,623.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:624.2,624.66 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:624.66,626.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/client.go:627.2,627.22 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:40.49,42.23 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:42.23,44.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:45.2,45.10 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:48.47,59.2 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:61.53,70.2 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:77.9,78.22 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:78.22,80.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:81.2,81.66 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:81.66,83.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:85.2,86.16 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:86.16,88.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:89.2,91.36 3 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:91.36,94.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:95.2,95.16 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:95.16,97.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:99.2,102.12 3 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:109.105,115.68 4 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:115.68,117.17 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:117.17,119.4 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:120.3,120.15 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:123.2,123.37 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:123.37,125.17 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:125.17,127.4 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:130.2,130.33 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:130.33,133.17 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:133.17,135.4 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:136.3,136.44 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:136.44,138.4 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:142.2,142.45 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:142.45,143.35 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:143.35,145.18 2 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:145.18,147.5 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:148.4,148.45 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:148.45,150.5 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:154.2,154.44 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:158.70,160.2 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:162.58,163.102 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:163.102,164.17 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:164.17,166.4 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:167.3,167.18 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:167.18,169.4 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:172.3,173.17 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:173.17,175.4 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:176.3,176.46 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:178.2,178.16 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:178.16,180.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:182.2,182.44 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:185.53,190.16 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:190.16,192.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:193.2,194.16 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:194.16,196.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:197.2,197.12 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:205.91,211.33 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:211.33,214.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:218.2,218.26 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:218.26,220.44 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:220.44,221.12 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:223.3,223.17 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:223.17,225.4 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:226.3,229.17 3 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:229.17,231.4 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:232.3,232.29 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:235.2,235.17 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:238.48,241.2 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:243.49,246.2 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:250.64,252.16 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:252.16,254.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/context.go:255.2,255.77 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/dedent.go:9.30,13.2 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/dockerfile.go:12.50,29.2 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:48.51,79.2 9 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:81.38,82.16 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:82.16,84.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:85.2,85.87 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:88.36,89.17 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:89.17,91.17 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:91.17,93.4 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:94.3,94.40 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:96.2,96.23 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:96.23,98.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:99.2,99.20 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:99.20,101.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:102.2,102.14 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:107.43,108.14 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:108.14,110.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:111.2,111.25 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:124.52,129.2 3 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:131.40,132.14 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:132.14,134.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:135.2,136.18 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:136.18,138.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:139.2,139.18 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:139.18,141.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:143.2,150.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:157.49,160.2 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:162.37,163.14 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:163.14,165.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:166.2,166.79 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:184.51,213.2 8 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:215.39,216.14 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:216.14,218.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:219.2,220.19 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:220.19,222.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:223.2,223.27 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:223.27,225.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:226.2,226.23 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:226.23,228.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:229.2,229.32 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:229.32,231.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:232.2,232.28 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:232.28,234.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:235.2,235.21 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:235.21,237.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:238.2,238.20 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:238.20,240.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:241.2,248.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:255.54,257.2 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:259.42,260.14 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:260.14,262.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:263.2,263.82 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:270.39,271.14 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:271.14,273.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:274.2,274.50 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:277.51,279.2 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:285.37,286.14 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:286.14,288.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:289.2,289.48 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:296.53,299.2 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:301.40,302.18 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:302.18,304.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:305.2,305.49 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:314.61,322.2 6 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:324.48,325.29 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:325.29,327.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:328.2,329.25 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:329.25,331.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:332.2,332.28 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:332.28,334.14 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:334.14,336.4 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:337.3,337.65 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:339.2,339.31 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:339.31,341.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:342.2,342.33 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:349.42,351.28 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:351.28,353.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:354.2,354.33 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:357.55,359.2 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:365.48,367.34 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:367.34,369.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:370.2,371.33 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:374.61,378.2 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/export.go:380.61,382.2 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/host.go:24.45,26.16 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/host.go:26.16,28.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/host.go:30.2,31.16 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/host.go:31.16,33.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/host.go:34.2,40.15 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/host.go:49.64,55.43 4 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/host.go:55.43,57.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/host.go:59.2,60.70 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/host.go:60.70,62.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/host.go:63.2,67.16 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/host.go:67.16,69.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/host.go:74.2,74.42 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/host.go:74.42,76.17 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/host.go:76.17,78.4 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/host.go:79.3,82.17 3 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/host.go:82.17,84.4 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/host.go:85.2,86.31 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/host.go:86.31,87.23 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/host.go:87.23,88.13 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/host.go:90.4,90.40 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/host.go:90.40,91.13 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/host.go:93.4,93.23 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/host.go:93.23,94.13 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/host.go:96.4,97.18 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/host.go:97.18,98.13 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/host.go:100.4,100.28 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/host.go:100.28,101.24 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/host.go:101.24,102.26 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/host.go:104.5,104.66 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/host.go:104.66,105.26 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/host.go:111.4,112.9 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/host.go:119.2,120.39 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/host.go:120.39,122.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/host.go:124.2,127.20 3 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:62.45,83.2 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:112.50,285.2 23 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:297.51,328.2 4 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:330.96,333.55 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:333.55,335.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:337.2,344.33 4 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:354.47,356.38 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:356.38,358.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:361.2,363.58 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:363.58,365.26 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:365.26,366.38 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:366.38,368.5 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:391.2,391.28 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:398.39,400.2 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:402.63,404.2 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:406.62,432.2 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:434.39,438.2 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:441.40,442.13 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:442.13,444.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:445.2,445.31 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:445.31,446.17 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:446.17,448.4 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:450.2,450.14 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:453.50,454.30 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:454.30,456.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:457.2,457.22 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:467.58,469.2 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:471.32,473.2 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:475.45,477.30 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:477.30,479.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:480.2,480.35 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:483.34,485.2 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:490.20,492.16 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:492.16,494.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:496.2,496.26 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:496.26,501.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:503.2,503.54 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:503.54,507.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:509.2,511.8 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:517.87,520.25 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:520.25,524.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:525.2,525.24 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:525.24,529.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:530.2,530.49 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:530.49,534.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:536.2,536.31 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:536.31,538.53 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:538.53,539.66 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:539.66,541.62 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:541.62,546.6 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:548.9,548.84 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:548.84,553.4 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:556.2,556.34 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:556.34,558.54 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:558.54,561.4 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:564.2,564.32 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:564.32,566.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:570.2,573.19 3 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:573.19,575.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:576.2,576.19 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:576.19,578.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:579.2,579.37 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:579.37,580.23 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:580.23,581.12 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:583.3,583.45 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:583.45,591.12 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:593.3,594.17 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:594.17,596.12 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:598.3,599.74 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:599.74,607.12 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:609.3,609.33 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:612.2,612.13 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:612.13,614.29 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:614.29,615.25 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:615.25,617.5 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:621.2,622.39 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:622.39,624.17 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:624.17,626.12 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:628.3,628.44 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:631.2,632.39 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:632.39,633.23 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:633.23,634.12 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:636.3,636.45 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:636.45,644.12 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:646.3,647.17 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:647.17,649.12 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:651.3,651.23 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:651.23,652.12 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:654.3,654.43 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:657.2,658.37 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:658.37,659.23 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:659.23,660.12 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:662.3,662.45 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:662.45,670.12 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:672.3,673.17 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:673.17,675.12 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:677.3,677.23 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:677.23,678.12 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:680.3,680.39 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:683.2,684.33 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:684.33,686.17 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:686.17,688.12 2 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:690.3,690.23 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:690.23,691.12 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:693.3,693.31 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:696.2,696.76 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:696.76,704.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:706.2,706.34 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:706.34,707.47 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:707.47,709.4 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:712.2,713.37 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:713.37,718.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:720.2,741.23 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:751.23,755.16 3 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:755.16,757.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:759.2,760.16 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:760.16,762.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:763.2,763.9 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:763.9,765.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:767.2,768.16 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:768.16,770.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:772.2,777.16 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:777.16,779.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:780.2,782.46 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:782.46,784.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:785.2,785.35 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:785.35,788.3 2 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:790.2,791.16 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:791.16,793.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:795.2,795.41 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:795.41,798.17 3 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:798.17,800.4 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:805.2,805.30 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:805.30,806.74 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:806.74,808.4 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:811.2,811.24 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:811.24,814.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:818.2,818.33 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:818.33,820.10 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:820.10,821.12 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:824.3,825.8 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:828.2,828.19 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:837.31,840.2 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:854.3,856.16 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:856.16,858.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:867.2,867.25 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:867.25,870.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:872.2,875.33 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:875.33,877.10 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:877.10,880.9 2 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:884.3,885.17 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:885.17,887.12 2 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:890.3,890.27 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:890.27,891.87 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:891.87,893.13 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:895.4,895.20 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:895.20,897.13 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:900.4,901.9 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:907.2,907.49 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:907.49,909.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:911.2,913.32 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:923.9,925.16 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:925.16,927.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:929.2,931.33 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:931.33,933.17 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:933.17,934.12 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:936.3,937.30 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:937.30,938.12 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:940.3,942.31 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:942.31,943.23 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:943.23,945.5 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:946.4,946.24 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:946.24,948.5 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:954.2,954.17 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:964.34,968.54 3 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:968.54,970.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:971.2,971.56 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:971.56,973.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:974.2,974.48 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:974.48,976.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:977.2,977.52 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:977.52,979.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:980.2,980.56 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:980.56,982.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:983.2,983.52 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:983.52,985.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:986.2,986.52 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:986.52,988.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:989.2,989.64 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:989.64,991.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:992.2,992.58 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:992.58,994.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:995.2,995.54 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:995.54,997.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:999.2,999.58 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:999.58,1001.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:1002.2,1002.50 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:1002.50,1004.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:1005.2,1005.28 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:1005.28,1007.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:1008.2,1008.34 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:1008.34,1010.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:1011.2,1011.52 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:1011.52,1013.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:1014.2,1014.56 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:1014.56,1016.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:1017.2,1017.28 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:1017.28,1019.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:1020.2,1020.28 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:1020.28,1022.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:1023.2,1023.52 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:1023.52,1025.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:1026.2,1026.44 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:1026.44,1028.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:1029.2,1029.46 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:1029.46,1031.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:1032.2,1032.50 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:1032.50,1034.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:1038.2,1038.68 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:1038.68,1040.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:1043.2,1048.16 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:1048.16,1050.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:1051.2,1051.30 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:1051.30,1053.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:1056.2,1056.50 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:1056.50,1058.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:1058.8,1059.42 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:1059.42,1061.74 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:1061.74,1062.13 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:1064.4,1065.9 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:1069.2,1073.8 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:1076.41,1078.16 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:1078.16,1080.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:1081.2,1082.16 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:1082.16,1084.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:1085.2,1085.12 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:1091.51,1093.16 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:1093.16,1095.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:1096.2,1097.48 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:1097.48,1099.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:1101.2,1104.16 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:1104.16,1106.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/image.go:1108.2,1108.28 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/images.go:40.46,50.2 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/images.go:52.50,71.2 4 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/images.go:78.32,81.2 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/images.go:89.24,93.16 3 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/images.go:93.16,95.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/images.go:97.2,97.13 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/images.go:97.13,99.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/images.go:103.2,104.16 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/images.go:104.16,106.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/images.go:108.2,109.19 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/images.go:117.44,122.16 4 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/images.go:122.16,124.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/images.go:132.2,133.84 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/images.go:133.84,136.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/images.go:137.2,137.85 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/images.go:137.85,140.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/images.go:141.2,141.16 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/images.go:141.16,143.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/images.go:145.2,145.49 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/images.go:145.49,147.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/images.go:149.2,149.32 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/images.go:157.48,159.16 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/images.go:159.16,161.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/images.go:176.2,176.74 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/images.go:176.74,184.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/images.go:186.2,186.35 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/images.go:186.35,187.61 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/images.go:187.61,195.4 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/images.go:198.2,198.28 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/images.go:201.82,203.16 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/images.go:203.16,205.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/images.go:206.2,208.70 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/images.go:208.70,210.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/images.go:211.2,211.12 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/images.go:219.34,223.26 3 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/images.go:223.26,225.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/images.go:226.2,226.52 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/images.go:226.52,228.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/images.go:229.2,229.52 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/images.go:229.52,231.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/images.go:232.2,232.54 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/images.go:232.54,234.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/images.go:237.2,241.8 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/images.go:248.19,251.55 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/images.go:251.55,253.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/images.go:255.2,259.33 4 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/network.go:15.60,30.2 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/platform.go:26.54,57.2 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/platform.go:59.35,61.2 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/preview.go:14.49,16.26 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/preview.go:16.26,17.17 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/preview.go:17.17,18.12 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/preview.go:20.3,20.29 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/preview.go:22.2,22.15 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/preview.go:28.43,29.16 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/preview.go:29.16,31.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/preview.go:32.2,32.16 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/preview.go:39.43,40.16 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/preview.go:40.16,42.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/preview.go:43.2,43.41 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/preview.go:52.51,53.16 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/preview.go:53.16,55.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/preview.go:56.2,56.44 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/preview.go:62.64,63.31 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/preview.go:63.31,65.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/preview.go:66.2,68.26 3 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/preview.go:68.26,69.20 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/preview.go:69.20,70.12 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/preview.go:72.3,72.20 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/preview.go:72.20,73.12 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/preview.go:75.3,75.22 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/preview.go:77.2,77.17 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/preview.go:82.59,83.38 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/preview.go:83.38,85.3 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/preview.go:87.2,89.29 3 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/preview.go:89.29,90.42 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/preview.go:90.42,91.12 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/preview.go:93.3,93.15 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/preview.go:96.2,99.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/ssh.go:14.43,31.2 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/ssh.go:33.30,34.16 1 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/ssh.go:34.16,36.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/ssh.go:38.2,40.22 2 1
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/ssh.go:40.22,42.3 1 0
|
||||
github.com/pulumi/pulumi-docker/provider/v4/internal/ssh.go:44.2,44.10 1 1
|
||||
27
provider/internal/dedent.go
Normal file
27
provider/internal/dedent.go
Normal file
@@ -0,0 +1,27 @@
|
||||
// Copyright 2024, 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 internal
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
dd "github.com/muesli/reflow/dedent"
|
||||
)
|
||||
|
||||
func dedent(s string) string {
|
||||
return strings.TrimSpace(dd.String(
|
||||
strings.ReplaceAll(s, `"`, "`"),
|
||||
))
|
||||
}
|
||||
51
provider/internal/dedent_test.go
Normal file
51
provider/internal/dedent_test.go
Normal file
@@ -0,0 +1,51 @@
|
||||
// Copyright 2024, 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 internal
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestDedent(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
name string
|
||||
given string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "simple case",
|
||||
given: `
|
||||
An optional map of named build-time argument variables to set during
|
||||
the Docker build. This flag allows you to pass build-time variables that
|
||||
can be accessed like environment variables inside the "RUN"
|
||||
instruction.`,
|
||||
want: `An optional map of named build-time argument variables to set during
|
||||
the Docker build. This flag allows you to pass build-time variables that
|
||||
can be accessed like environment variables inside the ` + "`RUN`\n" + `instruction.`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
actual := dedent(tt.given)
|
||||
assert.Equal(t, tt.want, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
213
provider/internal/deprecated/configencoding.go
Normal file
213
provider/internal/deprecated/configencoding.go
Normal file
@@ -0,0 +1,213 @@
|
||||
// Copyright 2024, 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 deprecated
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
|
||||
"github.com/pulumi/pulumi/pkg/v3/codegen/schema"
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/common/resource/plugin"
|
||||
)
|
||||
|
||||
// ConfigEncoding handles unmarshaling legacy JSON provider config.
|
||||
type ConfigEncoding struct {
|
||||
schema schema.ConfigSpec
|
||||
}
|
||||
|
||||
// New constructs a new config encoder for the provided spec.
|
||||
func New(s schema.ConfigSpec) *ConfigEncoding {
|
||||
return &ConfigEncoding{schema: s}
|
||||
}
|
||||
|
||||
func (*ConfigEncoding) tryUnwrapSecret(encoded any) (any, bool) {
|
||||
m, ok := encoded.(map[string]any)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
sig, ok := m["4dabf18193072939515e22adb298388d"]
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
ss, ok := sig.(string)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
if ss != "1b47061264138c4ac30d75fd1eb44270" {
|
||||
return nil, false
|
||||
}
|
||||
value, ok := m["value"]
|
||||
return value, ok
|
||||
}
|
||||
|
||||
func (enc *ConfigEncoding) convertStringToPropertyValue(s string, prop schema.PropertySpec) (
|
||||
resource.PropertyValue, error,
|
||||
) {
|
||||
// If the schema expects a string, we can just return this as-is.
|
||||
if prop.Type == "string" {
|
||||
return resource.NewStringProperty(s), nil
|
||||
}
|
||||
|
||||
// Otherwise, we will attempt to deserialize the input string as JSON and convert the result into a Pulumi
|
||||
// property. If the input string is empty, we will return an appropriate zero value.
|
||||
if s == "" {
|
||||
return enc.zeroValue(prop.Type), nil
|
||||
}
|
||||
|
||||
var jsonValue interface{}
|
||||
if err := json.Unmarshal([]byte(s), &jsonValue); err != nil {
|
||||
return resource.PropertyValue{}, err
|
||||
}
|
||||
|
||||
opts := enc.unmarshalOpts()
|
||||
|
||||
// Instead of using resource.NewPropertyValue, specialize it to detect nested json-encoded secrets.
|
||||
var replv func(encoded any) (resource.PropertyValue, bool)
|
||||
replv = func(encoded any) (resource.PropertyValue, bool) {
|
||||
encodedSecret, isSecret := enc.tryUnwrapSecret(encoded)
|
||||
if !isSecret {
|
||||
return resource.NewNullProperty(), false
|
||||
}
|
||||
|
||||
v := resource.NewPropertyValueRepl(encodedSecret, nil, replv)
|
||||
if opts.KeepSecrets {
|
||||
v = resource.MakeSecret(v)
|
||||
}
|
||||
|
||||
return v, true
|
||||
}
|
||||
|
||||
return resource.NewPropertyValueRepl(jsonValue, nil, replv), nil
|
||||
}
|
||||
|
||||
func (*ConfigEncoding) zeroValue(typ string) resource.PropertyValue {
|
||||
switch typ {
|
||||
case "boolean":
|
||||
return resource.NewPropertyValue(false)
|
||||
case "integer", "number":
|
||||
return resource.NewPropertyValue(0)
|
||||
case "array":
|
||||
return resource.NewPropertyValue([]interface{}{})
|
||||
default:
|
||||
return resource.NewPropertyValue(map[string]interface{}{})
|
||||
}
|
||||
}
|
||||
|
||||
func (enc *ConfigEncoding) unmarshalOpts() plugin.MarshalOptions {
|
||||
return plugin.MarshalOptions{
|
||||
Label: "config",
|
||||
KeepUnknowns: true,
|
||||
SkipNulls: true,
|
||||
RejectAssets: true,
|
||||
}
|
||||
}
|
||||
|
||||
// Like plugin.UnmarshalPropertyValue but overrides string parsing with convertStringToPropertyValue.
|
||||
func (enc *ConfigEncoding) unmarshalPropertyValue(key resource.PropertyKey,
|
||||
v *structpb.Value,
|
||||
) (*resource.PropertyValue, error) {
|
||||
opts := enc.unmarshalOpts()
|
||||
|
||||
pv, err := plugin.UnmarshalPropertyValue(key, v, enc.unmarshalOpts())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error unmarshalling property %q: %w", key, err)
|
||||
}
|
||||
|
||||
prop, ok := enc.schema.Variables[string(key)]
|
||||
|
||||
// Only apply JSON-encoded recognition for known fields.
|
||||
if !ok {
|
||||
return pv, nil
|
||||
}
|
||||
|
||||
var jsonString string
|
||||
var jsonStringDetected, jsonStringSecret bool
|
||||
|
||||
if pv.IsString() {
|
||||
jsonString = pv.StringValue()
|
||||
jsonStringDetected = true
|
||||
}
|
||||
|
||||
if opts.KeepSecrets && pv.IsSecret() && pv.SecretValue().Element.IsString() {
|
||||
jsonString = pv.SecretValue().Element.StringValue()
|
||||
jsonStringDetected = true
|
||||
jsonStringSecret = true
|
||||
}
|
||||
|
||||
if jsonStringDetected {
|
||||
v, err := enc.convertStringToPropertyValue(jsonString, prop)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error unmarshalling property %q: %w", key, err)
|
||||
}
|
||||
if jsonStringSecret {
|
||||
s := resource.MakeSecret(v)
|
||||
return &s, nil
|
||||
}
|
||||
return &v, nil
|
||||
}
|
||||
|
||||
// Computed sentinels are coming in as always having an empty string, but the encoding coerses them to a zero
|
||||
// value of the appropriate type.
|
||||
if pv.IsComputed() {
|
||||
el := pv.V.(resource.Computed).Element
|
||||
if el.IsString() && el.StringValue() == "" {
|
||||
res := resource.MakeComputed(enc.zeroValue(prop.Type))
|
||||
return &res, nil
|
||||
}
|
||||
}
|
||||
|
||||
return pv, nil
|
||||
}
|
||||
|
||||
// UnmarshalProperties is copied from plugin.UnmarshalProperties substituting plugin.UnmarshalPropertyValue.
|
||||
func (enc *ConfigEncoding) UnmarshalProperties(
|
||||
props *structpb.Struct,
|
||||
) (resource.PropertyMap, error) {
|
||||
opts := enc.unmarshalOpts()
|
||||
|
||||
result := make(resource.PropertyMap)
|
||||
|
||||
// First sort the keys so we enumerate them in order (in case errors happen, we want determinism).
|
||||
var keys []string
|
||||
if props != nil {
|
||||
for k := range props.Fields {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
}
|
||||
|
||||
// And now unmarshal every field it into the map.
|
||||
for _, key := range keys {
|
||||
pk := resource.PropertyKey(key)
|
||||
v, err := enc.unmarshalPropertyValue(pk, props.Fields[key])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if v != nil {
|
||||
if opts.SkipNulls && v.IsNull() {
|
||||
continue
|
||||
}
|
||||
if opts.SkipInternalKeys && resource.IsInternalPropertyKey(pk) {
|
||||
continue
|
||||
}
|
||||
result[pk] = *v
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
295
provider/internal/deprecated/configencoding_test.go
Normal file
295
provider/internal/deprecated/configencoding_test.go
Normal file
@@ -0,0 +1,295 @@
|
||||
// Copyright 2024, 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 deprecated
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
structpb "google.golang.org/protobuf/types/known/structpb"
|
||||
|
||||
"github.com/pulumi/pulumi/pkg/v3/codegen/schema"
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/common/resource/plugin"
|
||||
)
|
||||
|
||||
func TestConfigEncoding(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
type testCase struct {
|
||||
ty schema.TypeSpec
|
||||
v *structpb.Value
|
||||
pv resource.PropertyValue
|
||||
}
|
||||
|
||||
knownKey := "mykey"
|
||||
|
||||
makeEnc := func(typ schema.TypeSpec) *ConfigEncoding {
|
||||
return New(
|
||||
schema.ConfigSpec{
|
||||
Variables: map[string]schema.PropertySpec{
|
||||
knownKey: {
|
||||
TypeSpec: typ,
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
makeValue := func(x any) *structpb.Value {
|
||||
vv, err := structpb.NewValue(x)
|
||||
assert.NoErrorf(t, err, "structpb.NewValue failed")
|
||||
return vv
|
||||
}
|
||||
|
||||
checkUnmarshal := func(t *testing.T, tc testCase) {
|
||||
enc := makeEnc(tc.ty)
|
||||
pv, err := enc.unmarshalPropertyValue(resource.PropertyKey(knownKey), tc.v)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, pv)
|
||||
assert.Equal(t, tc.pv, *pv)
|
||||
}
|
||||
|
||||
turnaroundTestCases := []testCase{
|
||||
{
|
||||
schema.TypeSpec{Type: "boolean"},
|
||||
makeValue(`true`),
|
||||
resource.NewBoolProperty(true),
|
||||
},
|
||||
{
|
||||
schema.TypeSpec{Type: "boolean"},
|
||||
makeValue(`false`),
|
||||
resource.NewBoolProperty(false),
|
||||
},
|
||||
{
|
||||
schema.TypeSpec{Type: "integer"},
|
||||
makeValue(`0`),
|
||||
resource.NewNumberProperty(0),
|
||||
},
|
||||
{
|
||||
schema.TypeSpec{Type: "integer"},
|
||||
makeValue(`42`),
|
||||
resource.NewNumberProperty(42),
|
||||
},
|
||||
{
|
||||
schema.TypeSpec{Type: "number"},
|
||||
makeValue(`0`),
|
||||
resource.NewNumberProperty(0.0),
|
||||
},
|
||||
{
|
||||
schema.TypeSpec{Type: "number"},
|
||||
makeValue(`42.5`),
|
||||
resource.NewNumberProperty(42.5),
|
||||
},
|
||||
{
|
||||
schema.TypeSpec{Type: "string"},
|
||||
structpb.NewStringValue(""),
|
||||
resource.NewStringProperty(""),
|
||||
},
|
||||
{
|
||||
schema.TypeSpec{Type: "string"},
|
||||
structpb.NewStringValue("hello"),
|
||||
resource.NewStringProperty("hello"),
|
||||
},
|
||||
{
|
||||
schema.TypeSpec{Type: "array"},
|
||||
makeValue(`[]`),
|
||||
resource.NewArrayProperty([]resource.PropertyValue{}),
|
||||
},
|
||||
{
|
||||
schema.TypeSpec{Type: "array"},
|
||||
makeValue(`["hello","there"]`),
|
||||
resource.NewArrayProperty([]resource.PropertyValue{
|
||||
resource.NewStringProperty("hello"),
|
||||
resource.NewStringProperty("there"),
|
||||
}),
|
||||
},
|
||||
{
|
||||
schema.TypeSpec{Type: "object"},
|
||||
makeValue(`{}`),
|
||||
resource.NewObjectProperty(resource.PropertyMap{}),
|
||||
},
|
||||
{
|
||||
schema.TypeSpec{Type: "object"},
|
||||
makeValue(`{"key":"value"}`),
|
||||
resource.NewObjectProperty(resource.PropertyMap{
|
||||
"key": resource.NewStringProperty("value"),
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("turnaround", func(t *testing.T) {
|
||||
for i, tc := range turnaroundTestCases {
|
||||
tc := tc
|
||||
|
||||
t.Run(fmt.Sprintf("UnmarshalPropertyValue/%d", i), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
checkUnmarshal(t, tc)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("zero_values", func(t *testing.T) {
|
||||
// Historically the encoding was able to convert empty strings into type-appropriate zero values.
|
||||
cases := []testCase{
|
||||
{
|
||||
schema.TypeSpec{Type: "boolean"},
|
||||
makeValue(""),
|
||||
resource.NewBoolProperty(false),
|
||||
},
|
||||
{
|
||||
schema.TypeSpec{Type: "number"},
|
||||
makeValue(""),
|
||||
resource.NewNumberProperty(0.),
|
||||
},
|
||||
{
|
||||
schema.TypeSpec{Type: "integer"},
|
||||
makeValue(""),
|
||||
resource.NewNumberProperty(0),
|
||||
},
|
||||
{
|
||||
schema.TypeSpec{Type: "string"},
|
||||
makeValue(""),
|
||||
resource.NewStringProperty(""),
|
||||
},
|
||||
{
|
||||
schema.TypeSpec{Type: "object"},
|
||||
makeValue(""),
|
||||
resource.NewObjectProperty(make(resource.PropertyMap)),
|
||||
},
|
||||
{
|
||||
schema.TypeSpec{Type: "array"},
|
||||
makeValue(""),
|
||||
resource.NewArrayProperty([]resource.PropertyValue{}),
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
tc := tc
|
||||
|
||||
t.Run(fmt.Sprintf("%v", tc.ty), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
checkUnmarshal(t, tc)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("computed", func(t *testing.T) {
|
||||
unk := makeValue(plugin.UnknownStringValue)
|
||||
|
||||
for i, tc := range turnaroundTestCases {
|
||||
tc := tc
|
||||
|
||||
t.Run(fmt.Sprintf("UnmarshalPropertyValue/%d", i), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// Unknown sentinel unmarshals to a Computed with a type-appropriate zero value.
|
||||
checkUnmarshal(t, testCase{
|
||||
tc.ty,
|
||||
unk,
|
||||
resource.MakeComputed(makeEnc(tc.ty).zeroValue(tc.ty.Type)),
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("secret", func(t *testing.T) {
|
||||
// Unmarshalling happens with KeepSecrets=false, replacing them with the underlying values. This case
|
||||
// does not need to be tested.
|
||||
//
|
||||
// Marhalling however supports sending secrets back to the engine, intending to mark values as secret
|
||||
// that happen on paths that are declared as secret in the schema. Due to the limitation of the
|
||||
// JSON-in-proto-encoding, secrets are communicated imprecisely as an approximation: if any nested
|
||||
// element of a property is secret, the entire property is marshalled as secret.
|
||||
|
||||
var secretCases []testCase
|
||||
|
||||
pbSecret := func(v *structpb.Value) *structpb.Value {
|
||||
return structpb.NewStructValue(&structpb.Struct{Fields: map[string]*structpb.Value{
|
||||
"4dabf18193072939515e22adb298388d": makeValue("1b47061264138c4ac30d75fd1eb44270"),
|
||||
"value": v,
|
||||
}})
|
||||
}
|
||||
|
||||
for _, tc := range turnaroundTestCases {
|
||||
secretCases = append(secretCases, testCase{
|
||||
tc.ty,
|
||||
pbSecret(tc.v),
|
||||
resource.MakeSecret(tc.pv),
|
||||
})
|
||||
}
|
||||
|
||||
for i, tc := range secretCases {
|
||||
tc := tc
|
||||
|
||||
t.Run(fmt.Sprintf("secret/UnmarshalPropertyValue/%d", i), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Unmarshallin will remove secrts, so the expected value needs to be modified.
|
||||
tc.pv = tc.pv.SecretValue().Element
|
||||
checkUnmarshal(t, tc)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("tolerate secrets in Configure", func(t *testing.T) {
|
||||
// This is a bit of a histirocal quirk: the engine may send secrets to Configure before
|
||||
// receiving the response from Configure indicating that the provider does not want to receive
|
||||
// secrets. These are simply ignored. The engine does not currently send secrets to CheckConfig.
|
||||
// The engine does take care of making sure the secrets are stored as such in the statefile.
|
||||
//
|
||||
// Check here that unmarshalilng such values removes the secrets.
|
||||
checkUnmarshal(t, testCase{
|
||||
schema.TypeSpec{Type: "object"},
|
||||
pbSecret(makeValue(`{"key":"val"}`)),
|
||||
resource.NewObjectProperty(resource.PropertyMap{
|
||||
"key": resource.NewStringProperty("val"),
|
||||
}),
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
regressUnmarshalTestCases := []testCase{
|
||||
{
|
||||
schema.TypeSpec{Type: "array"},
|
||||
makeValue(`
|
||||
[
|
||||
{
|
||||
"address": "somewhere.org",
|
||||
"password": {
|
||||
"4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270",
|
||||
"value": "some-password"
|
||||
},
|
||||
"username": "some-user"
|
||||
}
|
||||
]`),
|
||||
resource.NewArrayProperty([]resource.PropertyValue{
|
||||
resource.NewObjectProperty(resource.PropertyMap{
|
||||
"address": resource.NewStringProperty("somewhere.org"),
|
||||
"password": resource.NewStringProperty("some-password"),
|
||||
"username": resource.NewStringProperty("some-user"),
|
||||
}),
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("regress-unmarshal", func(t *testing.T) {
|
||||
for i, tc := range regressUnmarshalTestCases {
|
||||
tc := tc
|
||||
t.Run(fmt.Sprintf("UnmarshalPropertyValue/%d", i), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
checkUnmarshal(t, tc)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
19
provider/internal/deprecated/doc.go
Normal file
19
provider/internal/deprecated/doc.go
Normal file
@@ -0,0 +1,19 @@
|
||||
// Copyright 2024, 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 deprecated vendors config parsing from pulumi-terraform-bridge.
|
||||
//
|
||||
// Originally taken from here:
|
||||
// https://github.com/pulumi/pulumi-terraform-bridge/blob/90733a0c7/pkg/tfbridge/config_encoding.go
|
||||
package deprecated
|
||||
63
provider/internal/doc.go
Normal file
63
provider/internal/doc.go
Normal file
@@ -0,0 +1,63 @@
|
||||
// Copyright 2024, 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 internal contains our clients, validation, and provider
|
||||
// implementation for interacting with Docker's buildx APIs.
|
||||
//
|
||||
// The provider has two primary modes of operation when building an image. The
|
||||
// default behavior is to use an embedded Docker CLI, which does not require to
|
||||
// actually be installed on a host in order to perform builds (a build daemon
|
||||
// must still be accessible locally or remotely). The second mode execs a
|
||||
// "docker-buildx" binary on the host to perform builds. This second mode was
|
||||
// added primarily for compatibility with Docker Build Cloud, which requires a
|
||||
// custom docker-buildx binary.
|
||||
//
|
||||
// # CLIs
|
||||
//
|
||||
// In both execution modes we have several CLI clients. The first client is
|
||||
// scoped to the host and initialized as part of the provider's Configure call.
|
||||
// We use this CLI for host-level operations, in particular when we potentially
|
||||
// initialize a new buildx builder and when we fetch existing credentials on
|
||||
// the host.
|
||||
//
|
||||
// Each operation then has a CLI instance scoped to the life of the operation.
|
||||
// This allows us to layer resource-scoped credentials on top of the host's
|
||||
// existing credentials, and in practice Docker seems to handle these
|
||||
// connections more reliably than a single CLI for all operations.
|
||||
//
|
||||
// # Credentials
|
||||
//
|
||||
// When using the embedded Docker client, secrets are communicated to the build
|
||||
// daemon natively via gRPC callbacks. When running in exec mode, credentials
|
||||
// must be communicated to the buildx binary via a configuration file. In order
|
||||
// to not pollute the host's existing credentials with e.g. short-lived ECR
|
||||
// tokens, we copy a small subset of the host's Docker config to a temporary
|
||||
// directory and use that for the lifetime of the exec operation.
|
||||
//
|
||||
// # Preview mode
|
||||
//
|
||||
// The pulumi-go-provider primarily operates on simple Go structs and doesn't
|
||||
// currently have a way to distinguish whether a value is unknown or empty. We
|
||||
// ignore anything that is a zero value during previews before we apply
|
||||
// validation or perform builds.
|
||||
//
|
||||
// # Diffs
|
||||
//
|
||||
// Another limitation of pulumi-go-provider is that it doesn't currently allow
|
||||
// us to override the default Diff behavior. We intentionally apply
|
||||
// "ignoreChanges" semantics to registry passwords, in order to reduce noise
|
||||
// and unnecessary updates, but as a result we have to re-implement Diff from
|
||||
// the ground up. This implementation is not nearly as rich as the default
|
||||
// experience and should be replaced when an alternative is available.
|
||||
package internal
|
||||
108
provider/internal/dockerfile.go
Normal file
108
provider/internal/dockerfile.go
Normal file
@@ -0,0 +1,108 @@
|
||||
// Copyright 2024, 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 internal
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
buildx "github.com/docker/buildx/build"
|
||||
"github.com/moby/buildkit/frontend/dockerfile/instructions"
|
||||
"github.com/moby/buildkit/frontend/dockerfile/parser"
|
||||
|
||||
"github.com/pulumi/pulumi-go-provider/infer"
|
||||
)
|
||||
|
||||
// Dockerfile references a local, remote, or inline Dockerfile.
|
||||
type Dockerfile struct {
|
||||
Location string `pulumi:"location,optional"`
|
||||
Inline string `pulumi:"inline,optional"`
|
||||
}
|
||||
|
||||
// Annotate sets docstrings on Dockerfile.
|
||||
func (d *Dockerfile) Annotate(a infer.Annotator) {
|
||||
a.Describe(&d.Location, dedent(`
|
||||
Location of the Dockerfile to use.
|
||||
|
||||
Can be a relative or absolute path to a local file, or a remote URL.
|
||||
|
||||
Defaults to "${context.location}/Dockerfile" if context is on-disk.
|
||||
|
||||
Conflicts with "inline".
|
||||
`))
|
||||
a.Describe(&d.Inline, dedent(`
|
||||
Raw Dockerfile contents.
|
||||
|
||||
Conflicts with "location".
|
||||
|
||||
Equivalent to invoking Docker with "-f -".
|
||||
`))
|
||||
}
|
||||
|
||||
func (d *Dockerfile) validate(preview bool, c *Context) error {
|
||||
if d.Location != "" && d.Inline != "" {
|
||||
return newCheckFailure(
|
||||
errors.New(`only specify "file" or "inline", not both`),
|
||||
"dockerfile",
|
||||
)
|
||||
}
|
||||
|
||||
if d.Location != "" {
|
||||
if buildx.IsRemoteURL(d.Location) {
|
||||
return nil
|
||||
}
|
||||
abs, err := filepath.Abs(d.Location)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f, err := os.Open(filepath.Clean(abs))
|
||||
if err != nil {
|
||||
return newCheckFailure(err, "dockerfile.location")
|
||||
}
|
||||
if err := parseDockerfile(f); err != nil {
|
||||
return newCheckFailure(err, "dockerfile.location")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if d.Inline != "" {
|
||||
err := parseDockerfile(strings.NewReader(d.Inline))
|
||||
if err != nil {
|
||||
return newCheckFailure(err, "dockerfile.inline")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if !preview && c != nil && !buildx.IsRemoteURL(c.Location) {
|
||||
return newCheckFailure(errors.New("missing 'location' or 'inline'"), "dockerfile")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseDockerfile(r io.Reader) error {
|
||||
parsed, err := parser.Parse(r)
|
||||
if err != nil {
|
||||
return newCheckFailure(err, "dockerfile")
|
||||
}
|
||||
_, _, err = instructions.Parse(parsed.AST)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
102
provider/internal/dockerfile_test.go
Normal file
102
provider/internal/dockerfile_test.go
Normal file
@@ -0,0 +1,102 @@
|
||||
// Copyright 2024, 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 internal
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestValidateDockerfile(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
name string
|
||||
d Dockerfile
|
||||
givenC Context
|
||||
preview bool
|
||||
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "relative",
|
||||
d: Dockerfile{
|
||||
Location: "../internal/../internal/testdata/noop/Dockerfile",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "missing file",
|
||||
d: Dockerfile{
|
||||
Location: "/does/not/exist/Dockerfile",
|
||||
},
|
||||
wantErr: "no such file",
|
||||
},
|
||||
{
|
||||
name: "invalid syntax",
|
||||
d: Dockerfile{
|
||||
Location: "testdata/Dockerfile.invalid",
|
||||
},
|
||||
wantErr: "unknown instruction: RUNN",
|
||||
},
|
||||
{
|
||||
name: "invalid syntax inline",
|
||||
d: Dockerfile{
|
||||
Inline: "RUNN it",
|
||||
},
|
||||
wantErr: "unknown instruction: RUNN",
|
||||
},
|
||||
{
|
||||
name: "valid syntax inline",
|
||||
d: Dockerfile{
|
||||
Inline: "FROM scratch",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unset",
|
||||
d: Dockerfile{},
|
||||
wantErr: "missing 'location' or 'inline'",
|
||||
},
|
||||
{
|
||||
name: "unset with remote context",
|
||||
d: Dockerfile{},
|
||||
givenC: Context{Location: "https://github.com/foobar"},
|
||||
},
|
||||
{
|
||||
name: "preview",
|
||||
d: Dockerfile{},
|
||||
preview: true,
|
||||
},
|
||||
{
|
||||
name: "over-specified",
|
||||
d: Dockerfile{Location: ".", Inline: "FROM scratch"},
|
||||
wantErr: `only specify "file" or "inline", not both`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
err := tt.d.validate(tt.preview, &tt.givenC)
|
||||
|
||||
if tt.wantErr == "" {
|
||||
assert.NoError(t, err)
|
||||
} else {
|
||||
assert.ErrorContains(t, err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
1862
provider/internal/embed/image-examples.md
Normal file
1862
provider/internal/embed/image-examples.md
Normal file
File diff suppressed because it is too large
Load Diff
224
provider/internal/embed/image-migration.md
Normal file
224
provider/internal/embed/image-migration.md
Normal file
@@ -0,0 +1,224 @@
|
||||
## Migrating Pulumi Docker v3 and v4 Image resources
|
||||
|
||||
This provider's `Image` resource provides a superset of functionality over the `Image` resources available in versions 3 and 4 of the Pulumi Docker provider.
|
||||
Existing `Image` resources can be converted to the docker-build `Image` resources with minor modifications.
|
||||
|
||||
### Behavioral differences
|
||||
|
||||
There are several key behavioral differences to keep in mind when transitioning images to the new `Image` resource.
|
||||
|
||||
#### Previews
|
||||
|
||||
Version `3.x` of the Pulumi Docker provider always builds images during preview operations.
|
||||
This is helpful as a safeguard to prevent "broken" images from merging, but users found the behavior unnecessarily redundant when running previews and updates locally.
|
||||
|
||||
Version `4.x` changed build-on-preview behavior to be opt-in.
|
||||
By default, `v4.x` `Image` resources do _not_ build during previews, but this behavior can be toggled with the `buildOnPreview` option.
|
||||
Several users reported outages due to the default behavior allowing bad images to accidentally sneak through CI.
|
||||
|
||||
The default behavior of this provider's `Image` resource is similar to `3.x` and will build images during previews.
|
||||
This behavior can be changed by specifying `buildOnPreview`.
|
||||
|
||||
#### Push behavior
|
||||
|
||||
Versions `3.x` and `4.x` of the Pulumi Docker provider attempt to push images to remote registries by default.
|
||||
They expose a `skipPush: true` option to disable pushing.
|
||||
|
||||
This provider's `Image` resource matches the Docker CLI's behavior and does not push images anywhere by default.
|
||||
|
||||
To push images to a registry you can include `push: true` (equivalent to Docker's `--push` flag) or configure an `export` of type `registry` (equivalent to Docker's `--output type=registry`).
|
||||
Like Docker, if an image is configured without exports you will see a warning with instructions for how to enable pushing, but the build will still proceed normally.
|
||||
|
||||
#### Secrets
|
||||
|
||||
Version `3.x` of the Pulumi Docker provider supports secrets by way of the `extraOptions` field.
|
||||
|
||||
Version `4.x` of the Pulumi Docker provider does not support secrets.
|
||||
|
||||
The `Image` resource supports secrets but does not require those secrets to exist on-disk or in environment variables.
|
||||
Instead, they should be passed directly as values.
|
||||
(Please be sure to familiarize yourself with Pulumi's [native secret handling](https://www.pulumi.com/docs/concepts/secrets/).)
|
||||
Pulumi also provides [ESC](https://www.pulumi.com/product/esc/) to make it easier to share secrets across stacks and environments.
|
||||
|
||||
#### Caching
|
||||
|
||||
Version `3.x` of the Pulumi Docker provider exposes `cacheFrom: bool | { stages: [...] }`.
|
||||
It builds targets individually and pushes them to separate images for caching.
|
||||
|
||||
Version `4.x` exposes a similar parameter `cacheFrom: { images: [...] }` which pushes and pulls inline caches.
|
||||
|
||||
Both versions 3 and 4 require specific environment variables to be set and deviate from Docker's native caching behavior.
|
||||
This can result in inefficient builds due to unnecessary image pulls, repeated file transfers, etc.
|
||||
|
||||
The `Image` resource delegates all caching behavior to Docker.
|
||||
`cacheFrom` and `cacheTo` options (equivalent to Docker's `--cache-to` and `--cache-from`) are exposed and provide additional cache targets, such as local disk, S3 storage, etc.
|
||||
|
||||
#### Outputs
|
||||
|
||||
Versions `3.x` and `4.x` of the provider exposed a `repoDigest` output which was a fully qualified tag with digest.
|
||||
In `4.x` this could also be a single sha256 hash if the image wasn't pushed.
|
||||
|
||||
Unlike earlier providers the `Image` resource can push multiple tags.
|
||||
As a convenience, it exposes a `ref` output consisting of a tag with digest as long as the image was pushed.
|
||||
If multiple tags were pushed this uses one at random.
|
||||
|
||||
If you need more control over tag references you can use the `digest` output, which is always a single sha256 hash as long as the image was exported somewhere.
|
||||
|
||||
#### Tag deletion and refreshes
|
||||
|
||||
Versions 3 and 4 of Pulumi Docker provider do not delete tags when the `Image` resource is deleted, nor do they confirm expected tags exist during `refresh` operations.
|
||||
|
||||
The `buidx.Image` will query your registries during `refresh` to ensure the expected tags exist.
|
||||
If any are missing a subsequent `update` will push them.
|
||||
|
||||
When a `Image` is deleted, it will _attempt_ to also delete any pushed tags.
|
||||
Deletion of remote tags is not guaranteed because not all registries support the manifest `DELETE` API (`docker.io` in particular).
|
||||
Manifests are _not_ deleted in the same way during updates -- to do so safely would require a full build to determine whether a Pulumi operation should be an update or update-replace.
|
||||
|
||||
Use the [`retainOnDelete: true`](https://www.pulumi.com/docs/concepts/options/retainondelete/) option if you do not want tags deleted.
|
||||
|
||||
### Example migration
|
||||
|
||||
Examples of "fully-featured" `v3` and `v4` `Image` resources are shown below, along with an example `Image` resource showing how they would look after migration.
|
||||
|
||||
The `v3` resource leverages `buildx` via a `DOCKER_BUILDKIT` environment variable and CLI flags passed in with `extraOption`.
|
||||
After migration, the environment variable is no longer needed and CLI flags are now properties on the `Image`.
|
||||
In almost all cases, properties of `Image` are named after the Docker CLI flag they correspond to.
|
||||
|
||||
The `v4` resource is less functional than its `v3` counterpart because it lacks the flexibility of `extraOptions`.
|
||||
It it is shown with parameters similar to the `v3` example for completeness.
|
||||
|
||||
{{% examples %}}
|
||||
## Example Usage
|
||||
{{% example %}}
|
||||
### v3/v4 migration
|
||||
|
||||
```typescript
|
||||
|
||||
// v3 Image
|
||||
const v3 = new docker.Image("v3-image", {
|
||||
imageName: "myregistry.com/user/repo:latest",
|
||||
localImageName: "local-tag",
|
||||
skipPush: false,
|
||||
build: {
|
||||
dockerfile: "./Dockerfile",
|
||||
context: "../app",
|
||||
target: "mytarget",
|
||||
args: {
|
||||
MY_BUILD_ARG: "foo",
|
||||
},
|
||||
env: {
|
||||
DOCKER_BUILDKIT: "1",
|
||||
},
|
||||
extraOptions: [
|
||||
"--cache-from",
|
||||
"type=registry,myregistry.com/user/repo:cache",
|
||||
"--cache-to",
|
||||
"type=registry,myregistry.com/user/repo:cache",
|
||||
"--add-host",
|
||||
"metadata.google.internal:169.254.169.254",
|
||||
"--secret",
|
||||
"id=mysecret,src=/local/secret",
|
||||
"--ssh",
|
||||
"default=/home/runner/.ssh/id_ed25519",
|
||||
"--network",
|
||||
"host",
|
||||
"--platform",
|
||||
"linux/amd64",
|
||||
],
|
||||
},
|
||||
registry: {
|
||||
server: "myregistry.com",
|
||||
username: "username",
|
||||
password: pulumi.secret("password"),
|
||||
},
|
||||
});
|
||||
|
||||
// v3 Image after migrating to docker-build.Image
|
||||
const v3Migrated = new dockerbuild.Image("v3-to-buildx", {
|
||||
tags: ["myregistry.com/user/repo:latest", "local-tag"],
|
||||
push: true,
|
||||
dockerfile: {
|
||||
location: "./Dockerfile",
|
||||
},
|
||||
context: {
|
||||
location: "../app",
|
||||
},
|
||||
target: "mytarget",
|
||||
buildArgs: {
|
||||
MY_BUILD_ARG: "foo",
|
||||
},
|
||||
cacheFrom: [{ registry: { ref: "myregistry.com/user/repo:cache" } }],
|
||||
cacheTo: [{ registry: { ref: "myregistry.com/user/repo:cache" } }],
|
||||
secrets: {
|
||||
mysecret: "value",
|
||||
},
|
||||
addHosts: ["metadata.google.internal:169.254.169.254"],
|
||||
ssh: {
|
||||
default: ["/home/runner/.ssh/id_ed25519"],
|
||||
},
|
||||
network: "host",
|
||||
platforms: ["linux/amd64"],
|
||||
registries: [{
|
||||
address: "myregistry.com",
|
||||
username: "username",
|
||||
password: pulumi.secret("password"),
|
||||
}],
|
||||
});
|
||||
|
||||
|
||||
// v4 Image
|
||||
const v4 = new docker.Image("v4-image", {
|
||||
imageName: "myregistry.com/user/repo:latest",
|
||||
skipPush: false,
|
||||
build: {
|
||||
dockerfile: "./Dockerfile",
|
||||
context: "../app",
|
||||
target: "mytarget",
|
||||
args: {
|
||||
MY_BUILD_ARG: "foo",
|
||||
},
|
||||
cacheFrom: {
|
||||
images: ["myregistry.com/user/repo:cache"],
|
||||
},
|
||||
addHosts: ["metadata.google.internal:169.254.169.254"],
|
||||
network: "host",
|
||||
platform: "linux/amd64",
|
||||
},
|
||||
buildOnPreview: true,
|
||||
registry: {
|
||||
server: "myregistry.com",
|
||||
username: "username",
|
||||
password: pulumi.secret("password"),
|
||||
},
|
||||
});
|
||||
|
||||
// v4 Image after migrating to docker-build.Image
|
||||
const v4Migrated = new dockerbuild.Image("v4-to-buildx", {
|
||||
tags: ["myregistry.com/user/repo:latest"],
|
||||
push: true,
|
||||
dockerfile: {
|
||||
location: "./Dockerfile",
|
||||
},
|
||||
context: {
|
||||
location: "../app",
|
||||
},
|
||||
target: "mytarget",
|
||||
buildArgs: {
|
||||
MY_BUILD_ARG: "foo",
|
||||
},
|
||||
cacheFrom: [{ registry: { ref: "myregistry.com/user/repo:cache" } }],
|
||||
cacheTo: [{ registry: { ref: "myregistry.com/user/repo:cache" } }],
|
||||
addHosts: ["metadata.google.internal:169.254.169.254"],
|
||||
network: "host",
|
||||
platforms: ["linux/amd64"],
|
||||
registries: [{
|
||||
address: "myregistry.com",
|
||||
username: "username",
|
||||
password: pulumi.secret("password"),
|
||||
}],
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
{{% /example %}}
|
||||
408
provider/internal/embed/index-examples.md
Normal file
408
provider/internal/embed/index-examples.md
Normal file
@@ -0,0 +1,408 @@
|
||||
{{% examples %}}
|
||||
## Example Usage
|
||||
{{% example %}}
|
||||
### Multi-platform registry caching
|
||||
|
||||
```typescript
|
||||
import * as pulumi from "@pulumi/pulumi";
|
||||
import * as docker_build from "@pulumi/docker-build";
|
||||
|
||||
const amd64 = new docker_build.Image("amd64", {
|
||||
cacheFrom: [{
|
||||
registry: {
|
||||
ref: "docker.io/pulumi/pulumi:cache-amd64",
|
||||
},
|
||||
}],
|
||||
cacheTo: [{
|
||||
registry: {
|
||||
mode: docker_build.CacheMode.Max,
|
||||
ref: "docker.io/pulumi/pulumi:cache-amd64",
|
||||
},
|
||||
}],
|
||||
context: {
|
||||
location: "app",
|
||||
},
|
||||
platforms: [docker_build.Platform.Linux_amd64],
|
||||
tags: ["docker.io/pulumi/pulumi:3.107.0-amd64"],
|
||||
});
|
||||
const arm64 = new docker_build.Image("arm64", {
|
||||
cacheFrom: [{
|
||||
registry: {
|
||||
ref: "docker.io/pulumi/pulumi:cache-arm64",
|
||||
},
|
||||
}],
|
||||
cacheTo: [{
|
||||
registry: {
|
||||
mode: docker_build.CacheMode.Max,
|
||||
ref: "docker.io/pulumi/pulumi:cache-arm64",
|
||||
},
|
||||
}],
|
||||
context: {
|
||||
location: "app",
|
||||
},
|
||||
platforms: [docker_build.Platform.Linux_arm64],
|
||||
tags: ["docker.io/pulumi/pulumi:3.107.0-arm64"],
|
||||
});
|
||||
const index = new docker_build.Index("index", {
|
||||
sources: [
|
||||
amd64.ref,
|
||||
arm64.ref,
|
||||
],
|
||||
tag: "docker.io/pulumi/pulumi:3.107.0",
|
||||
});
|
||||
export const ref = index.ref;
|
||||
```
|
||||
```python
|
||||
import pulumi
|
||||
import pulumi_docker_build as docker_build
|
||||
|
||||
amd64 = docker_build.Image("amd64",
|
||||
cache_from=[docker_build.CacheFromArgs(
|
||||
registry=docker_build.CacheFromRegistryArgs(
|
||||
ref="docker.io/pulumi/pulumi:cache-amd64",
|
||||
),
|
||||
)],
|
||||
cache_to=[docker_build.CacheToArgs(
|
||||
registry=docker_build.CacheToRegistryArgs(
|
||||
mode=docker_build.CacheMode.MAX,
|
||||
ref="docker.io/pulumi/pulumi:cache-amd64",
|
||||
),
|
||||
)],
|
||||
context=docker_build.BuildContextArgs(
|
||||
location="app",
|
||||
),
|
||||
platforms=[docker_build.Platform.LINUX_AMD64],
|
||||
tags=["docker.io/pulumi/pulumi:3.107.0-amd64"])
|
||||
arm64 = docker_build.Image("arm64",
|
||||
cache_from=[docker_build.CacheFromArgs(
|
||||
registry=docker_build.CacheFromRegistryArgs(
|
||||
ref="docker.io/pulumi/pulumi:cache-arm64",
|
||||
),
|
||||
)],
|
||||
cache_to=[docker_build.CacheToArgs(
|
||||
registry=docker_build.CacheToRegistryArgs(
|
||||
mode=docker_build.CacheMode.MAX,
|
||||
ref="docker.io/pulumi/pulumi:cache-arm64",
|
||||
),
|
||||
)],
|
||||
context=docker_build.BuildContextArgs(
|
||||
location="app",
|
||||
),
|
||||
platforms=[docker_build.Platform.LINUX_ARM64],
|
||||
tags=["docker.io/pulumi/pulumi:3.107.0-arm64"])
|
||||
index = docker_build.Index("index",
|
||||
sources=[
|
||||
amd64.ref,
|
||||
arm64.ref,
|
||||
],
|
||||
tag="docker.io/pulumi/pulumi:3.107.0")
|
||||
pulumi.export("ref", index.ref)
|
||||
```
|
||||
```csharp
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Pulumi;
|
||||
using DockerBuild = Pulumi.DockerBuild;
|
||||
|
||||
return await Deployment.RunAsync(() =>
|
||||
{
|
||||
var amd64 = new DockerBuild.Image("amd64", new()
|
||||
{
|
||||
CacheFrom = new[]
|
||||
{
|
||||
new DockerBuild.Inputs.CacheFromArgs
|
||||
{
|
||||
Registry = new DockerBuild.Inputs.CacheFromRegistryArgs
|
||||
{
|
||||
Ref = "docker.io/pulumi/pulumi:cache-amd64",
|
||||
},
|
||||
},
|
||||
},
|
||||
CacheTo = new[]
|
||||
{
|
||||
new DockerBuild.Inputs.CacheToArgs
|
||||
{
|
||||
Registry = new DockerBuild.Inputs.CacheToRegistryArgs
|
||||
{
|
||||
Mode = DockerBuild.CacheMode.Max,
|
||||
Ref = "docker.io/pulumi/pulumi:cache-amd64",
|
||||
},
|
||||
},
|
||||
},
|
||||
Context = new DockerBuild.Inputs.BuildContextArgs
|
||||
{
|
||||
Location = "app",
|
||||
},
|
||||
Platforms = new[]
|
||||
{
|
||||
DockerBuild.Platform.Linux_amd64,
|
||||
},
|
||||
Tags = new[]
|
||||
{
|
||||
"docker.io/pulumi/pulumi:3.107.0-amd64",
|
||||
},
|
||||
});
|
||||
|
||||
var arm64 = new DockerBuild.Image("arm64", new()
|
||||
{
|
||||
CacheFrom = new[]
|
||||
{
|
||||
new DockerBuild.Inputs.CacheFromArgs
|
||||
{
|
||||
Registry = new DockerBuild.Inputs.CacheFromRegistryArgs
|
||||
{
|
||||
Ref = "docker.io/pulumi/pulumi:cache-arm64",
|
||||
},
|
||||
},
|
||||
},
|
||||
CacheTo = new[]
|
||||
{
|
||||
new DockerBuild.Inputs.CacheToArgs
|
||||
{
|
||||
Registry = new DockerBuild.Inputs.CacheToRegistryArgs
|
||||
{
|
||||
Mode = DockerBuild.CacheMode.Max,
|
||||
Ref = "docker.io/pulumi/pulumi:cache-arm64",
|
||||
},
|
||||
},
|
||||
},
|
||||
Context = new DockerBuild.Inputs.BuildContextArgs
|
||||
{
|
||||
Location = "app",
|
||||
},
|
||||
Platforms = new[]
|
||||
{
|
||||
DockerBuild.Platform.Linux_arm64,
|
||||
},
|
||||
Tags = new[]
|
||||
{
|
||||
"docker.io/pulumi/pulumi:3.107.0-arm64",
|
||||
},
|
||||
});
|
||||
|
||||
var index = new DockerBuild.Index("index", new()
|
||||
{
|
||||
Sources = new[]
|
||||
{
|
||||
amd64.Ref,
|
||||
arm64.Ref,
|
||||
},
|
||||
Tag = "docker.io/pulumi/pulumi:3.107.0",
|
||||
});
|
||||
|
||||
return new Dictionary<string, object?>
|
||||
{
|
||||
["ref"] = index.Ref,
|
||||
};
|
||||
});
|
||||
|
||||
```
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/pulumi/pulumi-docker-build/sdk/go/dockerbuild"
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
|
||||
)
|
||||
|
||||
func main() {
|
||||
pulumi.Run(func(ctx *pulumi.Context) error {
|
||||
amd64, err := dockerbuild.NewImage(ctx, "amd64", &dockerbuild.ImageArgs{
|
||||
CacheFrom: dockerbuild.CacheFromArray{
|
||||
&dockerbuild.CacheFromArgs{
|
||||
Registry: &dockerbuild.CacheFromRegistryArgs{
|
||||
Ref: pulumi.String("docker.io/pulumi/pulumi:cache-amd64"),
|
||||
},
|
||||
},
|
||||
},
|
||||
CacheTo: dockerbuild.CacheToArray{
|
||||
&dockerbuild.CacheToArgs{
|
||||
Registry: &dockerbuild.CacheToRegistryArgs{
|
||||
Mode: dockerbuild.CacheModeMax,
|
||||
Ref: pulumi.String("docker.io/pulumi/pulumi:cache-amd64"),
|
||||
},
|
||||
},
|
||||
},
|
||||
Context: &dockerbuild.BuildContextArgs{
|
||||
Location: pulumi.String("app"),
|
||||
},
|
||||
Platforms: docker - build.PlatformArray{
|
||||
dockerbuild.Platform_Linux_amd64,
|
||||
},
|
||||
Tags: pulumi.StringArray{
|
||||
pulumi.String("docker.io/pulumi/pulumi:3.107.0-amd64"),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
arm64, err := dockerbuild.NewImage(ctx, "arm64", &dockerbuild.ImageArgs{
|
||||
CacheFrom: dockerbuild.CacheFromArray{
|
||||
&dockerbuild.CacheFromArgs{
|
||||
Registry: &dockerbuild.CacheFromRegistryArgs{
|
||||
Ref: pulumi.String("docker.io/pulumi/pulumi:cache-arm64"),
|
||||
},
|
||||
},
|
||||
},
|
||||
CacheTo: dockerbuild.CacheToArray{
|
||||
&dockerbuild.CacheToArgs{
|
||||
Registry: &dockerbuild.CacheToRegistryArgs{
|
||||
Mode: dockerbuild.CacheModeMax,
|
||||
Ref: pulumi.String("docker.io/pulumi/pulumi:cache-arm64"),
|
||||
},
|
||||
},
|
||||
},
|
||||
Context: &dockerbuild.BuildContextArgs{
|
||||
Location: pulumi.String("app"),
|
||||
},
|
||||
Platforms: docker - build.PlatformArray{
|
||||
dockerbuild.Platform_Linux_arm64,
|
||||
},
|
||||
Tags: pulumi.StringArray{
|
||||
pulumi.String("docker.io/pulumi/pulumi:3.107.0-arm64"),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
index, err := dockerbuild.NewIndex(ctx, "index", &dockerbuild.IndexArgs{
|
||||
Sources: pulumi.StringArray{
|
||||
amd64.Ref,
|
||||
arm64.Ref,
|
||||
},
|
||||
Tag: pulumi.String("docker.io/pulumi/pulumi:3.107.0"),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx.Export("ref", index.Ref)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
```
|
||||
```yaml
|
||||
description: Multi-platform registry caching
|
||||
name: registry-caching
|
||||
outputs:
|
||||
ref: ${index.ref}
|
||||
resources:
|
||||
amd64:
|
||||
properties:
|
||||
cacheFrom:
|
||||
- registry:
|
||||
ref: docker.io/pulumi/pulumi:cache-amd64
|
||||
cacheTo:
|
||||
- registry:
|
||||
mode: max
|
||||
ref: docker.io/pulumi/pulumi:cache-amd64
|
||||
context:
|
||||
location: app
|
||||
platforms:
|
||||
- linux/amd64
|
||||
tags:
|
||||
- docker.io/pulumi/pulumi:3.107.0-amd64
|
||||
type: docker-build:Image
|
||||
arm64:
|
||||
properties:
|
||||
cacheFrom:
|
||||
- registry:
|
||||
ref: docker.io/pulumi/pulumi:cache-arm64
|
||||
cacheTo:
|
||||
- registry:
|
||||
mode: max
|
||||
ref: docker.io/pulumi/pulumi:cache-arm64
|
||||
context:
|
||||
location: app
|
||||
platforms:
|
||||
- linux/arm64
|
||||
tags:
|
||||
- docker.io/pulumi/pulumi:3.107.0-arm64
|
||||
type: docker-build:Image
|
||||
index:
|
||||
properties:
|
||||
sources:
|
||||
- ${amd64.ref}
|
||||
- ${arm64.ref}
|
||||
tag: docker.io/pulumi/pulumi:3.107.0
|
||||
type: docker-build:Index
|
||||
runtime: yaml
|
||||
```
|
||||
```java
|
||||
package generated_program;
|
||||
|
||||
import com.pulumi.Context;
|
||||
import com.pulumi.Pulumi;
|
||||
import com.pulumi.core.Output;
|
||||
import com.pulumi.dockerbuild.Image;
|
||||
import com.pulumi.dockerbuild.ImageArgs;
|
||||
import com.pulumi.dockerbuild.inputs.CacheFromArgs;
|
||||
import com.pulumi.dockerbuild.inputs.CacheFromRegistryArgs;
|
||||
import com.pulumi.dockerbuild.inputs.CacheToArgs;
|
||||
import com.pulumi.dockerbuild.inputs.CacheToRegistryArgs;
|
||||
import com.pulumi.dockerbuild.inputs.BuildContextArgs;
|
||||
import com.pulumi.dockerbuild.Index;
|
||||
import com.pulumi.dockerbuild.IndexArgs;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
public class App {
|
||||
public static void main(String[] args) {
|
||||
Pulumi.run(App::stack);
|
||||
}
|
||||
|
||||
public static void stack(Context ctx) {
|
||||
var amd64 = new Image("amd64", ImageArgs.builder()
|
||||
.cacheFrom(CacheFromArgs.builder()
|
||||
.registry(CacheFromRegistryArgs.builder()
|
||||
.ref("docker.io/pulumi/pulumi:cache-amd64")
|
||||
.build())
|
||||
.build())
|
||||
.cacheTo(CacheToArgs.builder()
|
||||
.registry(CacheToRegistryArgs.builder()
|
||||
.mode("max")
|
||||
.ref("docker.io/pulumi/pulumi:cache-amd64")
|
||||
.build())
|
||||
.build())
|
||||
.context(BuildContextArgs.builder()
|
||||
.location("app")
|
||||
.build())
|
||||
.platforms("linux/amd64")
|
||||
.tags("docker.io/pulumi/pulumi:3.107.0-amd64")
|
||||
.build());
|
||||
|
||||
var arm64 = new Image("arm64", ImageArgs.builder()
|
||||
.cacheFrom(CacheFromArgs.builder()
|
||||
.registry(CacheFromRegistryArgs.builder()
|
||||
.ref("docker.io/pulumi/pulumi:cache-arm64")
|
||||
.build())
|
||||
.build())
|
||||
.cacheTo(CacheToArgs.builder()
|
||||
.registry(CacheToRegistryArgs.builder()
|
||||
.mode("max")
|
||||
.ref("docker.io/pulumi/pulumi:cache-arm64")
|
||||
.build())
|
||||
.build())
|
||||
.context(BuildContextArgs.builder()
|
||||
.location("app")
|
||||
.build())
|
||||
.platforms("linux/arm64")
|
||||
.tags("docker.io/pulumi/pulumi:3.107.0-arm64")
|
||||
.build());
|
||||
|
||||
var index = new Index("index", IndexArgs.builder()
|
||||
.sources(
|
||||
amd64.ref(),
|
||||
arm64.ref())
|
||||
.tag("docker.io/pulumi/pulumi:3.107.0")
|
||||
.build());
|
||||
|
||||
ctx.export("ref", index.ref());
|
||||
}
|
||||
}
|
||||
```
|
||||
{{% /example %}}
|
||||
{{% /examples %}}
|
||||
475
provider/internal/export.go
Normal file
475
provider/internal/export.go
Normal file
@@ -0,0 +1,475 @@
|
||||
// Copyright 2024, 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 internal
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
controllerapi "github.com/docker/buildx/controller/pb"
|
||||
"github.com/docker/buildx/util/buildflags"
|
||||
|
||||
"github.com/pulumi/pulumi-go-provider/infer"
|
||||
)
|
||||
|
||||
var (
|
||||
_ fmt.Stringer = (*Export)(nil)
|
||||
_ fmt.Stringer = (*ExportDocker)(nil)
|
||||
_ fmt.Stringer = (*ExportImage)(nil)
|
||||
_ fmt.Stringer = (*ExportLocal)(nil)
|
||||
_ fmt.Stringer = (*ExportOCI)(nil)
|
||||
_ fmt.Stringer = (*ExportRegistry)(nil)
|
||||
_ fmt.Stringer = (*ExportTar)(nil)
|
||||
_ fmt.Stringer = ExportWithAnnotations{}
|
||||
_ fmt.Stringer = ExportWithCompression{}
|
||||
_ fmt.Stringer = ExportWithNames{}
|
||||
_ fmt.Stringer = ExportWithOCI{}
|
||||
_ infer.Annotated = (*Export)(nil)
|
||||
_ infer.Annotated = (*ExportDocker)(nil)
|
||||
_ infer.Annotated = (*ExportImage)(nil)
|
||||
_ infer.Annotated = (*ExportLocal)(nil)
|
||||
_ infer.Annotated = (*ExportOCI)(nil)
|
||||
_ infer.Annotated = (*ExportRegistry)(nil)
|
||||
_ infer.Annotated = (*ExportTar)(nil)
|
||||
)
|
||||
|
||||
// Export is a "union" type for all of our available `--output` options.
|
||||
type Export struct {
|
||||
Tar *ExportTar `pulumi:"tar,optional"`
|
||||
Local *ExportLocal `pulumi:"local,optional"`
|
||||
Registry *ExportRegistry `pulumi:"registry,optional"`
|
||||
Image *ExportImage `pulumi:"image,optional"`
|
||||
OCI *ExportOCI `pulumi:"oci,optional"`
|
||||
Docker *ExportDocker `pulumi:"docker,optional"`
|
||||
CacheOnly *ExportCacheOnly `pulumi:"cacheonly,optional"`
|
||||
Raw Raw `pulumi:"raw,optional"`
|
||||
|
||||
Disabled bool `pulumi:"disabled,optional"`
|
||||
}
|
||||
|
||||
// Annotate sets docstrings on Export.
|
||||
func (e *Export) Annotate(a infer.Annotator) {
|
||||
a.Describe(&e.Tar, dedent(`
|
||||
Export to a local directory as a tarball.`,
|
||||
))
|
||||
a.Describe(&e.Local, dedent(`
|
||||
Export to a local directory as files and directories.`,
|
||||
))
|
||||
a.Describe(&e.Registry, dedent(`
|
||||
Identical to the Image exporter, but pushes by default.`,
|
||||
))
|
||||
a.Describe(&e.Image, dedent(`
|
||||
Outputs the build result into a container image format.`,
|
||||
))
|
||||
a.Describe(&e.OCI, dedent(`
|
||||
Identical to the Docker exporter but uses OCI media types by default.`,
|
||||
))
|
||||
a.Describe(&e.Docker, dedent(`
|
||||
Export as a Docker image layout.`,
|
||||
))
|
||||
a.Describe(&e.Raw, dedent(`
|
||||
A raw string as you would provide it to the Docker CLI (e.g.,
|
||||
"type=docker")`,
|
||||
))
|
||||
a.Describe(&e.CacheOnly, dedent(`
|
||||
A no-op export. Helpful for silencing the 'no exports' warning if you
|
||||
just want to populate caches.
|
||||
`))
|
||||
|
||||
a.Describe(&e.Disabled, dedent(`
|
||||
When "true" this entry will be excluded. Defaults to "false".
|
||||
`))
|
||||
}
|
||||
|
||||
// String returns a CLI-encoded value for this `--output` entry, or an empty
|
||||
// string if disabled. `validate` should be called to ensure only one entry was
|
||||
// set.
|
||||
func (e Export) String() string {
|
||||
if e.Disabled {
|
||||
return ""
|
||||
}
|
||||
return join(e.Tar, e.Local, e.Registry, e.Image, e.OCI, e.Docker, e.CacheOnly, e.Raw)
|
||||
}
|
||||
|
||||
// pushed returns true if the export would result in a registry push.
|
||||
func (e Export) pushed() bool {
|
||||
if e.Raw != "" {
|
||||
exp, err := buildflags.ParseExports([]string{e.Raw.String()})
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return exp[0].Attrs["push"] == "true"
|
||||
}
|
||||
if e.Registry != nil {
|
||||
return e.Registry.Push == nil || *e.Registry.Push
|
||||
}
|
||||
if e.Image != nil {
|
||||
return e.Image.Push != nil && *e.Image.Push
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (e Export) validate(preview bool, tags []string) (*controllerapi.ExportEntry, error) {
|
||||
if strings.Count(e.String(), "type=") > 1 {
|
||||
return nil, errors.New("exports should only specify one export type")
|
||||
}
|
||||
ee, err := buildflags.ParseExports([]string{e.String()})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
exp := ee[0]
|
||||
if len(tags) == 0 && isRegistryPush(exp) && exp.Attrs["name"] == "" {
|
||||
return nil, errors.New(
|
||||
"at least one tag or export name is needed when pushing to a registry",
|
||||
)
|
||||
}
|
||||
if !preview {
|
||||
return exp, nil
|
||||
}
|
||||
|
||||
// Don't perform registry pushes during previews.
|
||||
if exp.Type == "image" {
|
||||
exp.Attrs["push"] = "false"
|
||||
}
|
||||
return exp, nil
|
||||
}
|
||||
|
||||
// ExportCacheOnly is a dummy/no-op --cache-to entry. It exists only to help
|
||||
// silence the "no exports configured" warning. By using this the user signals
|
||||
// that they intentionally do not want exports, and only caches will be
|
||||
// populated as a result.
|
||||
type ExportCacheOnly struct{}
|
||||
|
||||
// String returns the CLI-encoded value of these export options, or an empty
|
||||
// string if the receiver is nil.
|
||||
func (e *ExportCacheOnly) String() string {
|
||||
if e == nil {
|
||||
return ""
|
||||
}
|
||||
return "type=cacheonly"
|
||||
}
|
||||
|
||||
// ExportDocker pushes the final image to the local build daemon.
|
||||
type ExportDocker struct {
|
||||
ExportWithOCI
|
||||
ExportWithCompression
|
||||
ExportWithAnnotations
|
||||
ExportWithNames
|
||||
|
||||
Dest string `pulumi:"dest,optional"`
|
||||
Tar *bool `pulumi:"tar,optional"`
|
||||
}
|
||||
|
||||
// Annotate sets docstrings and defaults on ExportDocker.
|
||||
func (e *ExportDocker) Annotate(a infer.Annotator) {
|
||||
a.SetDefault(&e.Tar, true)
|
||||
|
||||
a.Describe(&e.Dest, "The local export path.")
|
||||
a.Describe(&e.Tar, "Bundle the output into a tarball layout.")
|
||||
}
|
||||
|
||||
// String returns the CLI-encoded value of these export options, or an empty
|
||||
// string if the receiver is nil.
|
||||
func (e *ExportDocker) String() string {
|
||||
if e == nil {
|
||||
return ""
|
||||
}
|
||||
parts := []string{}
|
||||
if e.Dest != "" {
|
||||
parts = append(parts, "dest="+e.Dest)
|
||||
}
|
||||
if e.Tar != nil {
|
||||
parts = append(parts, fmt.Sprintf("tar=%t", *e.Tar))
|
||||
}
|
||||
|
||||
return join(
|
||||
Raw("type=docker"),
|
||||
Raw(strings.Join(parts, ",")),
|
||||
e.ExportWithOCI,
|
||||
e.ExportWithCompression,
|
||||
e.ExportWithAnnotations,
|
||||
e.ExportWithNames,
|
||||
)
|
||||
}
|
||||
|
||||
// ExportOCI is a cache that defaults to using OCI media types.
|
||||
type ExportOCI struct {
|
||||
ExportDocker
|
||||
}
|
||||
|
||||
// Annotate sets docstrings and defaults on ExportOCI.
|
||||
func (e *ExportOCI) Annotate(a infer.Annotator) {
|
||||
a.SetDefault(&e.OCI, true)
|
||||
a.Describe(&e.OCI, "Use OCI media types in exporter manifests.")
|
||||
}
|
||||
|
||||
func (e *ExportOCI) String() string {
|
||||
if e == nil {
|
||||
return ""
|
||||
}
|
||||
return strings.Replace(e.ExportDocker.String(), "type=docker", "type=oci", 1)
|
||||
}
|
||||
|
||||
// ExportImage can push the final image to remote registries.
|
||||
type ExportImage struct {
|
||||
ExportWithOCI
|
||||
ExportWithCompression
|
||||
ExportWithNames
|
||||
ExportWithAnnotations
|
||||
|
||||
Push *bool `pulumi:"push,optional"`
|
||||
PushByDigest *bool `pulumi:"pushByDigest,optional"`
|
||||
Insecure *bool `pulumi:"insecure,optional"`
|
||||
DanglingNamePrefix string `pulumi:"danglingNamePrefix,optional"`
|
||||
NameCanonical *bool `pulumi:"nameCanonical,optional"`
|
||||
Unpack *bool `pulumi:"unpack,optional"`
|
||||
Store *bool `pulumi:"store,optional"`
|
||||
}
|
||||
|
||||
// Annotate sets docstrings and defaults on ExportImage.
|
||||
func (e *ExportImage) Annotate(a infer.Annotator) {
|
||||
a.SetDefault(&e.Store, true)
|
||||
|
||||
a.Describe(&e.Store, dedent(`
|
||||
Store resulting images to the worker's image store and ensure all of
|
||||
its blobs are in the content store.
|
||||
|
||||
Defaults to "true".
|
||||
|
||||
Ignored if the worker doesn't have image store (when using OCI workers,
|
||||
for example).
|
||||
`))
|
||||
a.Describe(&e.Push, dedent(`
|
||||
Push after creating the image. Defaults to "false".
|
||||
`))
|
||||
a.Describe(&e.DanglingNamePrefix, dedent(`
|
||||
Name image with "prefix@<digest>", used for anonymous images.
|
||||
`))
|
||||
a.Describe(&e.NameCanonical, dedent(`
|
||||
Add additional canonical name ("name@<digest>").
|
||||
`))
|
||||
a.Describe(&e.Insecure, dedent(`
|
||||
Allow pushing to an insecure registry.
|
||||
`))
|
||||
a.Describe(&e.PushByDigest, dedent(`
|
||||
Push image without name.
|
||||
`))
|
||||
a.Describe(&e.Unpack, dedent(`
|
||||
Unpack image after creation (for use with containerd). Defaults to
|
||||
"false".
|
||||
`))
|
||||
}
|
||||
|
||||
// String returns the CLI-encoded value of these export options, or an empty
|
||||
// string if the receiver is nil.
|
||||
func (e *ExportImage) String() string {
|
||||
if e == nil {
|
||||
return ""
|
||||
}
|
||||
parts := []string{}
|
||||
if e.Push != nil {
|
||||
parts = append(parts, fmt.Sprintf("push=%t", *e.Push))
|
||||
}
|
||||
if e.PushByDigest != nil {
|
||||
parts = append(parts, fmt.Sprintf("push-by-digest=%t", *e.PushByDigest))
|
||||
}
|
||||
if e.Insecure != nil {
|
||||
parts = append(parts, fmt.Sprintf("insecure=%t", *e.Insecure))
|
||||
}
|
||||
if e.DanglingNamePrefix != "" {
|
||||
parts = append(parts, "dangling-name-prefix="+e.DanglingNamePrefix)
|
||||
}
|
||||
if e.NameCanonical != nil {
|
||||
parts = append(parts, fmt.Sprintf("name-canonical=%t", *e.NameCanonical))
|
||||
}
|
||||
if e.Unpack != nil {
|
||||
parts = append(parts, fmt.Sprintf("unpack=%t", *e.Unpack))
|
||||
}
|
||||
if e.Store != nil {
|
||||
parts = append(parts, fmt.Sprintf("store=%t", *e.Store))
|
||||
}
|
||||
return join(
|
||||
Raw("type=image"),
|
||||
Raw(strings.Join(parts, ",")),
|
||||
e.ExportWithOCI,
|
||||
e.ExportWithCompression,
|
||||
e.ExportWithNames,
|
||||
e.ExportWithAnnotations,
|
||||
)
|
||||
}
|
||||
|
||||
// ExportRegistry is equivalent to ExportImage but defaults to push=true.
|
||||
type ExportRegistry struct {
|
||||
ExportImage
|
||||
}
|
||||
|
||||
// Annotate sets docstrings and defaults on ExportRegistry.
|
||||
func (e *ExportRegistry) Annotate(a infer.Annotator) {
|
||||
a.Describe(&e.Push, dedent(`
|
||||
Push after creating the image. Defaults to "true".
|
||||
`))
|
||||
a.SetDefault(&e.Push, true)
|
||||
}
|
||||
|
||||
// String returns the CLI-encoded value of these export options, or an empty
|
||||
// string if the receiver is nil.
|
||||
func (e *ExportRegistry) String() string {
|
||||
if e == nil {
|
||||
return ""
|
||||
}
|
||||
return strings.Replace(e.ExportImage.String(), "type=image", "type=registry", 1)
|
||||
}
|
||||
|
||||
// ExportLocal writes the final image to disk.
|
||||
type ExportLocal struct {
|
||||
Dest string `pulumi:"dest"`
|
||||
}
|
||||
|
||||
// String returns the CLI-encoded value of these export options, or an empty
|
||||
// string if the receiver is nil.
|
||||
func (e *ExportLocal) String() string {
|
||||
if e == nil {
|
||||
return ""
|
||||
}
|
||||
return "type=local,dest=" + e.Dest
|
||||
}
|
||||
|
||||
// Annotate sets docstrings on ExportLocal.
|
||||
func (e *ExportLocal) Annotate(a infer.Annotator) {
|
||||
a.Describe(&e.Dest, "Output path.")
|
||||
}
|
||||
|
||||
// ExportTar is an export that uses the tar format for exporting.
|
||||
type ExportTar struct {
|
||||
ExportLocal
|
||||
}
|
||||
|
||||
func (e *ExportTar) String() string {
|
||||
if e == nil {
|
||||
return ""
|
||||
}
|
||||
return "type=tar,dest=" + e.Dest
|
||||
}
|
||||
|
||||
// ExportWithOCI is an export that support OCI media types.
|
||||
type ExportWithOCI struct {
|
||||
OCI *bool `pulumi:"ociMediaTypes,optional"`
|
||||
}
|
||||
|
||||
// Annotate sets defaults on ExportWithOCI.
|
||||
func (c *ExportWithOCI) Annotate(a infer.Annotator) {
|
||||
a.SetDefault(&c.OCI, false)
|
||||
a.Describe(&c.OCI, "Use OCI media types in exporter manifests.")
|
||||
}
|
||||
|
||||
func (c ExportWithOCI) String() string {
|
||||
if c.OCI == nil {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("oci-mediatypes=%t", *c.OCI)
|
||||
}
|
||||
|
||||
// ExportWithCompression is an export with options to configure compression
|
||||
// settings.
|
||||
type ExportWithCompression struct {
|
||||
Compression *CompressionType `pulumi:"compression,optional"`
|
||||
CompressionLevel int `pulumi:"compressionLevel,optional"`
|
||||
ForceCompression *bool `pulumi:"forceCompression,optional"`
|
||||
}
|
||||
|
||||
// Annotate sets docstrings and defaults on ExportWithCompression.
|
||||
func (e *ExportWithCompression) Annotate(a infer.Annotator) {
|
||||
gzip := Gzip
|
||||
a.SetDefault(&e.Compression, &gzip)
|
||||
a.SetDefault(&e.CompressionLevel, 0)
|
||||
a.SetDefault(&e.ForceCompression, false)
|
||||
|
||||
a.Describe(&e.Compression, "The compression type to use.")
|
||||
a.Describe(&e.CompressionLevel, "Compression level from 0 to 22.")
|
||||
a.Describe(&e.ForceCompression, "Forcefully apply compression.")
|
||||
}
|
||||
|
||||
func (e ExportWithCompression) String() string {
|
||||
if e.CompressionLevel == 0 {
|
||||
return ""
|
||||
}
|
||||
parts := []string{}
|
||||
if e.Compression != nil {
|
||||
parts = append(parts, fmt.Sprintf("compression=%s", *e.Compression))
|
||||
}
|
||||
if e.CompressionLevel > 0 {
|
||||
cl := e.CompressionLevel
|
||||
if cl > 22 {
|
||||
cl = 22
|
||||
}
|
||||
parts = append(parts, fmt.Sprintf("compression-level=%d", cl))
|
||||
}
|
||||
if e.ForceCompression != nil {
|
||||
parts = append(parts, fmt.Sprintf("force-compression=%t", *e.ForceCompression))
|
||||
}
|
||||
return strings.Join(parts, ",")
|
||||
}
|
||||
|
||||
// ExportWithNames is an export with configurable names (tags).
|
||||
type ExportWithNames struct {
|
||||
Names []string `pulumi:"names,optional"`
|
||||
}
|
||||
|
||||
func (e ExportWithNames) String() string {
|
||||
parts := []string{}
|
||||
for _, n := range e.Names {
|
||||
parts = append(parts, "name="+n)
|
||||
}
|
||||
return strings.Join(parts, ",")
|
||||
}
|
||||
|
||||
// Annotate sets docstrings on ExportWithNames.
|
||||
func (e *ExportWithNames) Annotate(a infer.Annotator) {
|
||||
a.Describe(
|
||||
&e.Names,
|
||||
"Specify images names to export. This is overridden if tags are already specified.",
|
||||
)
|
||||
}
|
||||
|
||||
// ExportWithAnnotations is an export with configurable annotations.
|
||||
type ExportWithAnnotations struct {
|
||||
Annotations map[string]string `pulumi:"annotations,optional"`
|
||||
}
|
||||
|
||||
func (e ExportWithAnnotations) String() string {
|
||||
parts := []string{}
|
||||
for k, v := range e.Annotations {
|
||||
parts = append(parts, fmt.Sprintf("annotation.%s=%s", k, v))
|
||||
}
|
||||
slices.Sort(parts)
|
||||
return strings.Join(parts, ",")
|
||||
}
|
||||
|
||||
// Annotate sets docstrings on ExportWithAnnotations.
|
||||
func (e *ExportWithAnnotations) Annotate(a infer.Annotator) {
|
||||
a.Describe(&e.Annotations, dedent(`
|
||||
Attach an arbitrary key/value annotation to the image.
|
||||
`))
|
||||
}
|
||||
|
||||
// isRegistryPush returns true if the ExportEntry results in an image pushed to
|
||||
// a registry.
|
||||
func isRegistryPush(export *controllerapi.ExportEntry) bool {
|
||||
// "type=registry" is shorthand for "type=image,push=true" so we only need
|
||||
// to check "image" types.
|
||||
return export.Type == "image" && export.Attrs["push"] == "true"
|
||||
}
|
||||
246
provider/internal/export_test.go
Normal file
246
provider/internal/export_test.go
Normal file
@@ -0,0 +1,246 @@
|
||||
// Copyright 2024, 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 internal
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
controllerapi "github.com/docker/buildx/controller/pb"
|
||||
"github.com/docker/buildx/util/buildflags"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
|
||||
)
|
||||
|
||||
func TestValidateExport(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
e Export
|
||||
givenTags []string
|
||||
preview bool
|
||||
|
||||
wantExp *controllerapi.ExportEntry
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "raw - no push on preview",
|
||||
preview: true,
|
||||
e: Export{Raw: "type=registry"},
|
||||
givenTags: []string{"docker.io/foo/bar"},
|
||||
wantExp: &controllerapi.ExportEntry{
|
||||
Type: "image",
|
||||
Attrs: map[string]string{"push": "false"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "raw - push requires tags",
|
||||
e: Export{Raw: "type=registry"},
|
||||
wantErr: "tag or export name is needed",
|
||||
},
|
||||
{
|
||||
name: "registry - no push on preview",
|
||||
preview: true,
|
||||
e: Export{Registry: &ExportRegistry{}},
|
||||
givenTags: []string{"docker.io/foo/bar"},
|
||||
wantExp: &controllerapi.ExportEntry{
|
||||
Type: "image",
|
||||
Attrs: map[string]string{"push": "false"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "registry - push requires tags",
|
||||
e: Export{Registry: &ExportRegistry{}},
|
||||
wantErr: "tag or export name is needed",
|
||||
},
|
||||
{
|
||||
name: "over-specified",
|
||||
e: Export{Raw: "type=registry", Registry: &ExportRegistry{}},
|
||||
wantErr: "specify one export type",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
e, err := tt.e.validate(tt.preview, tt.givenTags)
|
||||
if tt.wantErr == "" {
|
||||
assert.NoError(t, err)
|
||||
} else {
|
||||
assert.ErrorContains(t, err, tt.wantErr)
|
||||
}
|
||||
if tt.wantExp != nil {
|
||||
assert.Equal(t, tt.wantExp.Type, e.Type)
|
||||
assert.Equal(t, tt.wantExp.Attrs, e.Attrs)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExportString(t *testing.T) {
|
||||
t.Parallel()
|
||||
gzip := Gzip
|
||||
tests := []struct {
|
||||
name string
|
||||
given Export
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "tar",
|
||||
given: Export{Tar: &ExportTar{ExportLocal: ExportLocal{Dest: "/foo"}}},
|
||||
want: "type=tar,dest=/foo",
|
||||
},
|
||||
{
|
||||
name: "local",
|
||||
given: Export{Local: &ExportLocal{Dest: "/bar"}},
|
||||
want: "type=local,dest=/bar",
|
||||
},
|
||||
{
|
||||
name: "registry-with-compression",
|
||||
given: Export{Registry: &ExportRegistry{
|
||||
ExportImage: ExportImage{
|
||||
ExportWithCompression: ExportWithCompression{
|
||||
Compression: &gzip,
|
||||
CompressionLevel: 100,
|
||||
ForceCompression: pulumi.BoolRef(true),
|
||||
},
|
||||
},
|
||||
}},
|
||||
want: "type=registry,compression=gzip,compression-level=22,force-compression=true",
|
||||
},
|
||||
{
|
||||
name: "registry-without-push",
|
||||
given: Export{Registry: &ExportRegistry{
|
||||
ExportImage: ExportImage{
|
||||
Push: pulumi.BoolRef(false),
|
||||
},
|
||||
}},
|
||||
want: "type=registry,push=false",
|
||||
},
|
||||
{
|
||||
name: "image",
|
||||
given: Export{
|
||||
Image: &ExportImage{
|
||||
Push: pulumi.BoolRef(true),
|
||||
PushByDigest: pulumi.BoolRef(true),
|
||||
Insecure: pulumi.BoolRef(true),
|
||||
DanglingNamePrefix: "prefix",
|
||||
Unpack: pulumi.BoolRef(true),
|
||||
Store: pulumi.BoolRef(false),
|
||||
},
|
||||
},
|
||||
want: "type=image,push=true,push-by-digest=true,insecure=true,dangling-name-prefix=prefix,unpack=true,store=false",
|
||||
},
|
||||
{
|
||||
name: "oci-with-names",
|
||||
given: Export{OCI: &ExportOCI{
|
||||
ExportDocker: ExportDocker{
|
||||
ExportWithNames: ExportWithNames{
|
||||
Names: []string{"foo", "bar"},
|
||||
},
|
||||
},
|
||||
}},
|
||||
want: "type=oci,name=foo,name=bar",
|
||||
},
|
||||
{
|
||||
name: "docker-with-annotations",
|
||||
given: Export{Docker: &ExportDocker{
|
||||
ExportWithAnnotations: ExportWithAnnotations{
|
||||
Annotations: map[string]string{
|
||||
"foo": "bar",
|
||||
"boo": "baz",
|
||||
},
|
||||
},
|
||||
}},
|
||||
want: "type=docker,annotation.boo=baz,annotation.foo=bar",
|
||||
},
|
||||
{
|
||||
name: "raw",
|
||||
given: Export{Raw: Raw("type=docker")},
|
||||
want: "type=docker",
|
||||
},
|
||||
{
|
||||
name: "disabled",
|
||||
given: Export{Raw: Raw("type=docker"), Disabled: true},
|
||||
want: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
actual := tt.given.String()
|
||||
assert.Equal(t, tt.want, tt.given.String())
|
||||
|
||||
if tt.want != "" {
|
||||
// Our output should be parsable by Docker.
|
||||
_, err := buildflags.ParseExports([]string{actual})
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExportPushed(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
name string
|
||||
e Export
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "raw registry",
|
||||
e: Export{Raw: "type=registry"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "raw image",
|
||||
e: Export{Raw: "type=image"},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "registry with no push",
|
||||
e: Export{Registry: &ExportRegistry{}},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "registry with explicit push",
|
||||
e: Export{Registry: &ExportRegistry{ExportImage{Push: pulumi.BoolRef(false)}}},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "image with explicit push",
|
||||
e: Export{Image: &ExportImage{Push: pulumi.BoolRef(true)}},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "local",
|
||||
e: Export{Local: &ExportLocal{}},
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
actual := tt.e.pushed()
|
||||
assert.Equal(t, tt.want, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
163
provider/internal/host.go
Normal file
163
provider/internal/host.go
Normal file
@@ -0,0 +1,163 @@
|
||||
// Copyright 2024, 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 internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/docker/buildx/builder"
|
||||
"github.com/docker/buildx/store/storeutil"
|
||||
"github.com/docker/cli/cli/command"
|
||||
cfgtypes "github.com/docker/cli/cli/config/types"
|
||||
)
|
||||
|
||||
// host contains a host-level Docker CLI as well as a cache of initialized
|
||||
// builders. Operations on the host are serialized.
|
||||
type host struct {
|
||||
mu sync.Mutex
|
||||
cli command.Cli
|
||||
config *Config
|
||||
builders map[string]*cachedBuilder
|
||||
auths map[string]cfgtypes.AuthConfig
|
||||
}
|
||||
|
||||
func newHost(config *Config) (*host, error) {
|
||||
docker, err := newDockerCLI(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Load existing credentials into memory.
|
||||
auths, err := docker.ConfigFile().GetAllCredentials()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
h := &host{
|
||||
cli: docker,
|
||||
config: config,
|
||||
builders: map[string]*cachedBuilder{},
|
||||
auths: auths,
|
||||
}
|
||||
return h, err
|
||||
}
|
||||
|
||||
// builderFor ensures a builder is available and running. This is guarded by a
|
||||
// mutex to ensure other resources don't attempt to use the builder until it's
|
||||
// ready.
|
||||
//
|
||||
// If the build doesn't specify a builder by name, we will iterate through all
|
||||
// available builders until we find one that we can connect to.
|
||||
func (h *host) builderFor(build Build) (*cachedBuilder, error) {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
|
||||
opts := build.BuildOptions()
|
||||
|
||||
if b, ok := h.builders[opts.Builder]; ok {
|
||||
return b, nil
|
||||
}
|
||||
|
||||
txn, release, err := storeutil.GetStore(h.cli)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer release()
|
||||
|
||||
contextPathHash := opts.ContextPath
|
||||
if absContextPath, err := filepath.Abs(contextPathHash); err == nil {
|
||||
contextPathHash = absContextPath
|
||||
}
|
||||
b, err := builder.New(h.cli,
|
||||
builder.WithName(opts.Builder),
|
||||
builder.WithContextPathHash(contextPathHash),
|
||||
builder.WithStore(txn),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If we didn't request a particular builder, and we loaded a default
|
||||
// builder with an unsupported (docker) driver, then look for a builder we
|
||||
// do support.
|
||||
if b.Driver == "" && opts.Builder == "" {
|
||||
builders, err := builder.GetBuilders(h.cli, txn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nextbuilder:
|
||||
for _, bb := range builders {
|
||||
if bb.Driver == "" {
|
||||
continue
|
||||
}
|
||||
if err := bb.Validate(); err != nil {
|
||||
continue
|
||||
}
|
||||
if bb.Err() != nil {
|
||||
continue
|
||||
}
|
||||
nodes, err := bb.LoadNodes(context.Background())
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for idx := range nodes {
|
||||
n := nodes[idx]
|
||||
if n.Driver == nil {
|
||||
continue nextbuilder
|
||||
}
|
||||
if _, err := n.Driver.Dial(context.Background()); err != nil {
|
||||
continue nextbuilder
|
||||
}
|
||||
// TODO: Confirm the builder supports the requested platforms.
|
||||
}
|
||||
b = bb
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if b.Driver == "" && opts.Builder == "" {
|
||||
// If we STILL don't have a builder, create a docker-container instance.
|
||||
b, err = builder.Create(
|
||||
context.Background(),
|
||||
txn,
|
||||
h.cli,
|
||||
builder.CreateOpts{Driver: "docker-container"},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to load nodes in order to determine the builder's driver. Ignore
|
||||
// errors for "exec" builds because it's possible to request builders with
|
||||
// drivers that are unknown to us.
|
||||
nodes, err := b.LoadNodes(context.Background())
|
||||
if err != nil && !build.ShouldExec() {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cached := &cachedBuilder{name: b.Name, driver: b.Driver, nodes: nodes}
|
||||
h.builders[opts.Builder] = cached
|
||||
|
||||
return cached, nil
|
||||
}
|
||||
|
||||
// cachedBuilder caches the builders we've loaded. Repeatedly fetching them can
|
||||
// sometimes result in EOF errors from the daemon, especially when under load.
|
||||
type cachedBuilder struct {
|
||||
name string
|
||||
driver string
|
||||
nodes []builder.Node
|
||||
}
|
||||
1012
provider/internal/image.go
Normal file
1012
provider/internal/image.go
Normal file
File diff suppressed because it is too large
Load Diff
994
provider/internal/image_test.go
Normal file
994
provider/internal/image_test.go
Normal file
@@ -0,0 +1,994 @@
|
||||
// Copyright 2024, 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 internal
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
_ "github.com/docker/buildx/driver/docker-container"
|
||||
|
||||
"github.com/docker/distribution/reference"
|
||||
"github.com/moby/buildkit/client"
|
||||
"github.com/moby/buildkit/exporter/containerimage/exptypes"
|
||||
"github.com/regclient/regclient/types/descriptor"
|
||||
"github.com/regclient/regclient/types/platform"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/mock/gomock"
|
||||
|
||||
provider "github.com/pulumi/pulumi-go-provider"
|
||||
"github.com/pulumi/pulumi-go-provider/integration"
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/common/util/mapper"
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
|
||||
)
|
||||
|
||||
var _fakeURN = resource.NewURN("test", "provider", "a", "docker-build:index:Image", "test")
|
||||
|
||||
func TestImageLifecycle(t *testing.T) {
|
||||
t.Parallel()
|
||||
noClient := func(t *testing.T) Client {
|
||||
ctrl := gomock.NewController(t)
|
||||
return NewMockClient(ctrl)
|
||||
}
|
||||
|
||||
_, err := reference.ParseNamed("docker.io/pulumibot/buildkit-e2e")
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
op func(t *testing.T) integration.Operation
|
||||
client func(t *testing.T) Client
|
||||
}{
|
||||
{
|
||||
name: "happy path builds",
|
||||
client: func(t *testing.T) Client {
|
||||
ctrl := gomock.NewController(t)
|
||||
c := NewMockClient(ctrl)
|
||||
c.EXPECT().BuildKitEnabled().Return(true, nil).AnyTimes()
|
||||
c.EXPECT().Build(gomock.Any(), gomock.AssignableToTypeOf(build{})).DoAndReturn(
|
||||
func(_ provider.Context, b Build) (*client.SolveResponse, error) {
|
||||
assert.Equal(t, "testdata/noop/Dockerfile", b.BuildOptions().DockerfileName)
|
||||
return &client.SolveResponse{
|
||||
ExporterResponse: map[string]string{
|
||||
exptypes.ExporterImageDigestKey: "sha256:98ea6e4f216f2fb4b69fff9b3a44842c38686ca685f3f55dc48c5d3fb1107be4",
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
).AnyTimes()
|
||||
c.EXPECT().Delete(gomock.Any(),
|
||||
"docker.io/pulumibot/buildkit-e2e@sha256:98ea6e4f216f2fb4b69fff9b3a44842c38686ca685f3f55dc48c5d3fb1107be4",
|
||||
).
|
||||
Return(nil)
|
||||
return c
|
||||
},
|
||||
op: func(t *testing.T) integration.Operation {
|
||||
return integration.Operation{
|
||||
Inputs: resource.PropertyMap{
|
||||
"push": resource.NewBoolProperty(false),
|
||||
"tags": resource.NewArrayProperty(
|
||||
[]resource.PropertyValue{
|
||||
resource.NewStringProperty("docker.io/pulumibot/buildkit-e2e"),
|
||||
resource.NewStringProperty("docker.io/pulumibot/buildkit-e2e:main"),
|
||||
},
|
||||
),
|
||||
"platforms": resource.NewArrayProperty(
|
||||
[]resource.PropertyValue{
|
||||
resource.NewStringProperty("linux/arm64"),
|
||||
resource.NewStringProperty("linux/amd64"),
|
||||
},
|
||||
),
|
||||
"context": resource.NewObjectProperty(resource.PropertyMap{
|
||||
"location": resource.NewStringProperty("testdata/noop"),
|
||||
}),
|
||||
"dockerfile": resource.NewObjectProperty(resource.PropertyMap{
|
||||
"location": resource.NewStringProperty("testdata/noop/Dockerfile"),
|
||||
}),
|
||||
"exports": resource.NewArrayProperty(
|
||||
[]resource.PropertyValue{
|
||||
resource.NewObjectProperty(resource.PropertyMap{
|
||||
"raw": resource.NewStringProperty("type=registry"),
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
"registries": resource.NewArrayProperty(
|
||||
[]resource.PropertyValue{
|
||||
resource.NewObjectProperty(resource.PropertyMap{
|
||||
"address": resource.NewStringProperty("fakeaddress"),
|
||||
"username": resource.NewStringProperty("fakeuser"),
|
||||
"password": resource.MakeSecret(
|
||||
resource.NewStringProperty("password"),
|
||||
),
|
||||
}),
|
||||
},
|
||||
),
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "tags are required when pushing",
|
||||
client: noClient,
|
||||
op: func(t *testing.T) integration.Operation {
|
||||
return integration.Operation{
|
||||
Inputs: resource.PropertyMap{
|
||||
"push": resource.NewBoolProperty(false),
|
||||
"tags": resource.NewArrayProperty([]resource.PropertyValue{}),
|
||||
"context": resource.NewObjectProperty(resource.PropertyMap{
|
||||
"location": resource.NewStringProperty("testdata/noop"),
|
||||
}),
|
||||
"exports": resource.NewArrayProperty(
|
||||
[]resource.PropertyValue{
|
||||
resource.NewObjectProperty(resource.PropertyMap{
|
||||
"raw": resource.NewStringProperty("type=registry"),
|
||||
}),
|
||||
},
|
||||
),
|
||||
},
|
||||
ExpectFailure: true,
|
||||
CheckFailures: []provider.CheckFailure{
|
||||
{
|
||||
Property: "exports[0]",
|
||||
Reason: "at least one tag or export name is needed when pushing to a registry",
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid exports",
|
||||
client: noClient,
|
||||
op: func(t *testing.T) integration.Operation {
|
||||
return integration.Operation{
|
||||
Inputs: resource.PropertyMap{
|
||||
"push": resource.NewBoolProperty(false),
|
||||
"tags": resource.NewArrayProperty(
|
||||
[]resource.PropertyValue{resource.NewStringProperty("invalid-exports")},
|
||||
),
|
||||
"exports": resource.NewArrayProperty(
|
||||
[]resource.PropertyValue{
|
||||
resource.NewObjectProperty(resource.PropertyMap{
|
||||
"raw": resource.NewStringProperty("type="),
|
||||
}),
|
||||
},
|
||||
),
|
||||
},
|
||||
ExpectFailure: true,
|
||||
CheckFailures: []provider.CheckFailure{{
|
||||
Property: "exports[0]",
|
||||
Reason: "type is required for output",
|
||||
}},
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "requires buildkit",
|
||||
client: func(t *testing.T) Client {
|
||||
ctrl := gomock.NewController(t)
|
||||
c := NewMockClient(ctrl)
|
||||
gomock.InOrder(
|
||||
c.EXPECT().BuildKitEnabled().Return(false, nil), // Preview.
|
||||
)
|
||||
return c
|
||||
},
|
||||
op: func(t *testing.T) integration.Operation {
|
||||
return integration.Operation{
|
||||
Inputs: resource.PropertyMap{
|
||||
"push": resource.NewBoolProperty(false),
|
||||
"tags": resource.NewArrayProperty(
|
||||
[]resource.PropertyValue{resource.NewStringProperty("foo")},
|
||||
),
|
||||
"context": resource.NewObjectProperty(resource.PropertyMap{
|
||||
"location": resource.NewStringProperty("testdata/noop"),
|
||||
}),
|
||||
},
|
||||
ExpectFailure: true,
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "error reading DOCKER_BUILDKIT",
|
||||
client: func(t *testing.T) Client {
|
||||
ctrl := gomock.NewController(t)
|
||||
c := NewMockClient(ctrl)
|
||||
gomock.InOrder(
|
||||
c.EXPECT().
|
||||
BuildKitEnabled().
|
||||
Return(false, errors.New("invalid DOCKER_BUILDKIT")), // Preview.
|
||||
)
|
||||
return c
|
||||
},
|
||||
op: func(t *testing.T) integration.Operation {
|
||||
return integration.Operation{
|
||||
Inputs: resource.PropertyMap{
|
||||
"push": resource.NewBoolProperty(false),
|
||||
"tags": resource.NewArrayProperty(
|
||||
[]resource.PropertyValue{resource.NewStringProperty("foo")},
|
||||
),
|
||||
"context": resource.NewObjectProperty(resource.PropertyMap{
|
||||
"location": resource.NewStringProperty("testdata/noop"),
|
||||
}),
|
||||
},
|
||||
ExpectFailure: true,
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "file defaults to Dockerfile",
|
||||
client: func(t *testing.T) Client {
|
||||
ctrl := gomock.NewController(t)
|
||||
c := NewMockClient(ctrl)
|
||||
c.EXPECT().BuildKitEnabled().Return(true, nil).AnyTimes()
|
||||
c.EXPECT().Build(gomock.Any(), gomock.AssignableToTypeOf(build{})).DoAndReturn(
|
||||
func(_ provider.Context, b Build) (*client.SolveResponse, error) {
|
||||
assert.Equal(t, "testdata/noop/Dockerfile", b.BuildOptions().DockerfileName)
|
||||
return &client.SolveResponse{
|
||||
ExporterResponse: map[string]string{"image.name": "test:latest"},
|
||||
}, nil
|
||||
},
|
||||
).AnyTimes()
|
||||
c.EXPECT().Delete(gomock.Any(), "default-dockerfile").Return(nil)
|
||||
return c
|
||||
},
|
||||
op: func(t *testing.T) integration.Operation {
|
||||
return integration.Operation{
|
||||
Inputs: resource.PropertyMap{
|
||||
"push": resource.NewBoolProperty(false),
|
||||
"tags": resource.NewArrayProperty(
|
||||
[]resource.PropertyValue{
|
||||
resource.NewStringProperty("default-dockerfile"),
|
||||
},
|
||||
),
|
||||
"context": resource.NewObjectProperty(resource.PropertyMap{
|
||||
"location": resource.NewStringProperty("testdata/noop"),
|
||||
}),
|
||||
},
|
||||
Hook: func(_, output resource.PropertyMap) {
|
||||
dockerfile := output["dockerfile"]
|
||||
require.NotNil(t, dockerfile)
|
||||
require.True(t, dockerfile.IsObject())
|
||||
location := dockerfile.ObjectValue()["location"]
|
||||
require.True(t, location.IsString())
|
||||
assert.Equal(t, "testdata/noop/Dockerfile", location.StringValue())
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
lc := integration.LifeCycleTest{
|
||||
Resource: "docker-build:index:Image",
|
||||
Create: tt.op(t),
|
||||
}
|
||||
s := newServer(tt.client(t))
|
||||
|
||||
err := s.Configure(provider.ConfigureRequest{})
|
||||
require.NoError(t, err)
|
||||
|
||||
lc.Run(t, s)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type errNotFound struct{}
|
||||
|
||||
func (errNotFound) NotFound() {}
|
||||
func (errNotFound) Error() string { return "not found " }
|
||||
|
||||
func TestDelete(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("image was already deleted", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctrl := gomock.NewController(t)
|
||||
client := NewMockClient(ctrl)
|
||||
client.EXPECT().
|
||||
Delete(gomock.Any(), "docker.io/pulumi/test@sha256:foo").
|
||||
Return(errNotFound{})
|
||||
|
||||
s := newServer(client)
|
||||
err := s.Configure(provider.ConfigureRequest{})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = s.Delete(provider.DeleteRequest{
|
||||
ID: "foo,bar",
|
||||
Urn: _fakeURN,
|
||||
Properties: resource.PropertyMap{
|
||||
"tags": resource.NewArrayProperty([]resource.PropertyValue{
|
||||
resource.NewStringProperty("docker.io/pulumi/test:foo"),
|
||||
}),
|
||||
"push": resource.NewBoolProperty(true),
|
||||
"digest": resource.NewStringProperty("sha256:foo"),
|
||||
"contextHash": resource.NewStringProperty(""),
|
||||
"ref": resource.NewStringProperty(""),
|
||||
},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRead(t *testing.T) {
|
||||
t.Parallel()
|
||||
tag := "docker.io/pulumi/pulumitest"
|
||||
digest := "sha256:3be99cafdcd80a8e620da56bdc215acab6213bb608d3d492c0ba1807128786a1"
|
||||
|
||||
ctrl := gomock.NewController(t)
|
||||
client := NewMockClient(ctrl)
|
||||
client.EXPECT().Inspect(gomock.Any(), fmt.Sprintf("%s:latest@%s", tag, digest)).Return(
|
||||
[]descriptor.Descriptor{
|
||||
{
|
||||
Platform: &platform.Platform{Architecture: "arm64"},
|
||||
},
|
||||
{
|
||||
Platform: &platform.Platform{Architecture: "unknown"},
|
||||
},
|
||||
}, nil)
|
||||
|
||||
s := newServer(client)
|
||||
err := s.Configure(provider.ConfigureRequest{})
|
||||
require.NoError(t, err)
|
||||
|
||||
resp, err := s.Read(provider.ReadRequest{
|
||||
ID: "my-image",
|
||||
Urn: _fakeURN,
|
||||
Properties: resource.PropertyMap{
|
||||
"exports": resource.NewArrayProperty([]resource.PropertyValue{
|
||||
resource.NewObjectProperty(resource.PropertyMap{
|
||||
"raw": resource.NewStringProperty("type=registry"),
|
||||
}),
|
||||
}),
|
||||
"tags": resource.NewArrayProperty([]resource.PropertyValue{
|
||||
resource.NewStringProperty(tag),
|
||||
}),
|
||||
"digest": resource.NewStringProperty(digest),
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, resp.Properties["exports"].ArrayValue()[0].ObjectValue()["manifest"])
|
||||
}
|
||||
|
||||
func TestImageDiff(t *testing.T) {
|
||||
t.Parallel()
|
||||
emptyDir := t.TempDir()
|
||||
host := Host
|
||||
|
||||
hash, err := hashBuildContext(emptyDir, "", nil)
|
||||
require.NoError(t, err)
|
||||
baseArgs := ImageArgs{
|
||||
Context: &BuildContext{Context: Context{Location: emptyDir}},
|
||||
Dockerfile: &Dockerfile{Location: "testdata/noop"},
|
||||
Tags: []string{},
|
||||
}
|
||||
baseState := ImageState{
|
||||
ContextHash: hash,
|
||||
ImageArgs: baseArgs,
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
olds func(*testing.T, ImageState) ImageState
|
||||
news func(*testing.T, ImageArgs) ImageArgs
|
||||
|
||||
wantChanges bool
|
||||
}{
|
||||
{
|
||||
name: "no diff if build context is unchanged",
|
||||
olds: func(*testing.T, ImageState) ImageState { return baseState },
|
||||
news: func(*testing.T, ImageArgs) ImageArgs { return baseArgs },
|
||||
wantChanges: false,
|
||||
},
|
||||
{
|
||||
name: "no diff if registry password changes",
|
||||
olds: func(_ *testing.T, s ImageState) ImageState {
|
||||
s.Registries = []Registry{{
|
||||
Address: "foo",
|
||||
Username: "foo",
|
||||
Password: "foo",
|
||||
}}
|
||||
return s
|
||||
},
|
||||
news: func(_ *testing.T, a ImageArgs) ImageArgs {
|
||||
a.Registries = []Registry{{
|
||||
Address: "foo",
|
||||
Username: "foo",
|
||||
Password: "DIFFERENT PASSWORD",
|
||||
}}
|
||||
return a
|
||||
},
|
||||
wantChanges: false,
|
||||
},
|
||||
{
|
||||
name: "no diff if pull=true but no exports",
|
||||
olds: func(_ *testing.T, is ImageState) ImageState {
|
||||
is.Pull = true
|
||||
return is
|
||||
},
|
||||
news: func(t *testing.T, ia ImageArgs) ImageArgs {
|
||||
ia.Pull = true
|
||||
return ia
|
||||
},
|
||||
wantChanges: false,
|
||||
},
|
||||
{
|
||||
name: "diff if pull=true with exports",
|
||||
olds: func(_ *testing.T, is ImageState) ImageState {
|
||||
is.Pull = true
|
||||
is.Load = true
|
||||
return is
|
||||
},
|
||||
news: func(t *testing.T, ia ImageArgs) ImageArgs {
|
||||
ia.Pull = true
|
||||
ia.Load = true
|
||||
return ia
|
||||
},
|
||||
wantChanges: true,
|
||||
},
|
||||
{
|
||||
name: "diff if build context changes",
|
||||
olds: func(*testing.T, ImageState) ImageState { return baseState },
|
||||
news: func(t *testing.T, a ImageArgs) ImageArgs {
|
||||
tmp := filepath.Join(a.Context.Location, "tmp")
|
||||
err := os.WriteFile(tmp, []byte{}, 0o600)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() { _ = os.Remove(tmp) })
|
||||
return a
|
||||
},
|
||||
wantChanges: true,
|
||||
},
|
||||
{
|
||||
name: "diff if registry added",
|
||||
olds: func(*testing.T, ImageState) ImageState { return baseState },
|
||||
news: func(_ *testing.T, a ImageArgs) ImageArgs {
|
||||
a.Registries = []Registry{{}}
|
||||
return a
|
||||
},
|
||||
wantChanges: true,
|
||||
},
|
||||
{
|
||||
name: "diff if registry user changes",
|
||||
olds: func(_ *testing.T, s ImageState) ImageState {
|
||||
s.Registries = []Registry{{
|
||||
Address: "foo",
|
||||
Username: "foo",
|
||||
Password: "foo",
|
||||
}}
|
||||
return s
|
||||
},
|
||||
news: func(_ *testing.T, a ImageArgs) ImageArgs {
|
||||
a.Registries = []Registry{{
|
||||
Address: "DIFFERENT USER",
|
||||
Username: "foo",
|
||||
Password: "foo",
|
||||
}}
|
||||
return a
|
||||
},
|
||||
wantChanges: true,
|
||||
},
|
||||
{
|
||||
name: "diff if buildArgs changes",
|
||||
olds: func(*testing.T, ImageState) ImageState { return baseState },
|
||||
news: func(_ *testing.T, a ImageArgs) ImageArgs {
|
||||
a.BuildArgs = map[string]string{
|
||||
"foo": "bar",
|
||||
}
|
||||
return a
|
||||
},
|
||||
wantChanges: true,
|
||||
},
|
||||
{
|
||||
name: "diff if pull changes",
|
||||
olds: func(*testing.T, ImageState) ImageState { return baseState },
|
||||
news: func(t *testing.T, ia ImageArgs) ImageArgs {
|
||||
ia.Pull = true
|
||||
return ia
|
||||
},
|
||||
wantChanges: true,
|
||||
},
|
||||
{
|
||||
name: "diff if load changes",
|
||||
olds: func(*testing.T, ImageState) ImageState { return baseState },
|
||||
news: func(t *testing.T, ia ImageArgs) ImageArgs {
|
||||
ia.Load = true
|
||||
return ia
|
||||
},
|
||||
wantChanges: true,
|
||||
},
|
||||
{
|
||||
name: "diff if push changes",
|
||||
olds: func(*testing.T, ImageState) ImageState { return baseState },
|
||||
news: func(t *testing.T, ia ImageArgs) ImageArgs {
|
||||
ia.Push = true
|
||||
return ia
|
||||
},
|
||||
wantChanges: true,
|
||||
},
|
||||
{
|
||||
name: "diff if buildOnPreview doesn't change",
|
||||
olds: func(*testing.T, ImageState) ImageState { return baseState },
|
||||
news: func(t *testing.T, ia ImageArgs) ImageArgs {
|
||||
val := true
|
||||
ia.BuildOnPreview = &val
|
||||
return ia
|
||||
},
|
||||
wantChanges: true,
|
||||
},
|
||||
{
|
||||
name: "diff if buildOnPreview changes",
|
||||
olds: func(*testing.T, ImageState) ImageState { return baseState },
|
||||
news: func(t *testing.T, ia ImageArgs) ImageArgs {
|
||||
val := false
|
||||
ia.BuildOnPreview = &val
|
||||
return ia
|
||||
},
|
||||
wantChanges: true,
|
||||
},
|
||||
{
|
||||
name: "diff if ssh changes",
|
||||
olds: func(*testing.T, ImageState) ImageState { return baseState },
|
||||
news: func(t *testing.T, ia ImageArgs) ImageArgs {
|
||||
ia.SSH = []SSH{{ID: "default"}}
|
||||
return ia
|
||||
},
|
||||
wantChanges: true,
|
||||
},
|
||||
{
|
||||
name: "diff if hosts change",
|
||||
olds: func(*testing.T, ImageState) ImageState { return baseState },
|
||||
news: func(t *testing.T, ia ImageArgs) ImageArgs {
|
||||
ia.AddHosts = []string{"localhost"}
|
||||
return ia
|
||||
},
|
||||
wantChanges: true,
|
||||
},
|
||||
{
|
||||
name: "diff if cacheFrom changes",
|
||||
olds: func(*testing.T, ImageState) ImageState { return baseState },
|
||||
news: func(_ *testing.T, a ImageArgs) ImageArgs {
|
||||
a.CacheFrom = []CacheFrom{{Raw: "a"}}
|
||||
return a
|
||||
},
|
||||
wantChanges: true,
|
||||
},
|
||||
{
|
||||
name: "diff if cacheTo changes",
|
||||
olds: func(*testing.T, ImageState) ImageState { return baseState },
|
||||
news: func(_ *testing.T, a ImageArgs) ImageArgs {
|
||||
a.CacheTo = []CacheTo{{Raw: "a"}}
|
||||
return a
|
||||
},
|
||||
wantChanges: true,
|
||||
},
|
||||
{
|
||||
name: "diff if context changes",
|
||||
olds: func(*testing.T, ImageState) ImageState { return baseState },
|
||||
news: func(_ *testing.T, a ImageArgs) ImageArgs {
|
||||
a.Context = &BuildContext{Context: Context{Location: "testdata/ignores"}}
|
||||
return a
|
||||
},
|
||||
wantChanges: true,
|
||||
},
|
||||
{
|
||||
name: "diff if named context changes",
|
||||
olds: func(*testing.T, ImageState) ImageState { return baseState },
|
||||
news: func(_ *testing.T, a ImageArgs) ImageArgs {
|
||||
a.Context = &BuildContext{Named: NamedContexts{"foo": Context{Location: "bar"}}}
|
||||
return a
|
||||
},
|
||||
wantChanges: true,
|
||||
},
|
||||
{
|
||||
name: "diff if network changes",
|
||||
olds: func(*testing.T, ImageState) ImageState { return baseState },
|
||||
news: func(_ *testing.T, a ImageArgs) ImageArgs {
|
||||
a.Network = &host
|
||||
return a
|
||||
},
|
||||
wantChanges: true,
|
||||
},
|
||||
{
|
||||
name: "diff if dockerfile location changes",
|
||||
olds: func(*testing.T, ImageState) ImageState { return baseState },
|
||||
news: func(_ *testing.T, a ImageArgs) ImageArgs {
|
||||
a.Dockerfile = &Dockerfile{Location: "testdata/ignores/basedir/Dockerfile"}
|
||||
return a
|
||||
},
|
||||
wantChanges: true,
|
||||
},
|
||||
{
|
||||
name: "diff if dockerfile inline changes",
|
||||
olds: func(*testing.T, ImageState) ImageState { return baseState },
|
||||
news: func(_ *testing.T, a ImageArgs) ImageArgs {
|
||||
a.Dockerfile = &Dockerfile{Inline: "FROM scratch"}
|
||||
return a
|
||||
},
|
||||
wantChanges: true,
|
||||
},
|
||||
{
|
||||
name: "diff if platforms change",
|
||||
olds: func(*testing.T, ImageState) ImageState { return baseState },
|
||||
news: func(_ *testing.T, a ImageArgs) ImageArgs {
|
||||
a.Platforms = []Platform{"linux/amd64"}
|
||||
return a
|
||||
},
|
||||
wantChanges: true,
|
||||
},
|
||||
{
|
||||
name: "diff if pull changes",
|
||||
olds: func(*testing.T, ImageState) ImageState { return baseState },
|
||||
news: func(_ *testing.T, a ImageArgs) ImageArgs {
|
||||
a.Pull = true
|
||||
return a
|
||||
},
|
||||
wantChanges: true,
|
||||
},
|
||||
{
|
||||
name: "diff if builder changes",
|
||||
olds: func(*testing.T, ImageState) ImageState { return baseState },
|
||||
news: func(_ *testing.T, a ImageArgs) ImageArgs {
|
||||
a.Builder = &BuilderConfig{Name: "foo"}
|
||||
return a
|
||||
},
|
||||
wantChanges: true,
|
||||
},
|
||||
{
|
||||
name: "diff if tags change",
|
||||
olds: func(*testing.T, ImageState) ImageState { return baseState },
|
||||
news: func(_ *testing.T, a ImageArgs) ImageArgs {
|
||||
a.Tags = []string{"foo"}
|
||||
return a
|
||||
},
|
||||
wantChanges: true,
|
||||
},
|
||||
{
|
||||
name: "diff if exports change",
|
||||
olds: func(*testing.T, ImageState) ImageState { return baseState },
|
||||
news: func(_ *testing.T, a ImageArgs) ImageArgs {
|
||||
a.Exports = []Export{{Raw: "foo"}}
|
||||
return a
|
||||
},
|
||||
wantChanges: true,
|
||||
},
|
||||
{
|
||||
name: "diff if target changes",
|
||||
olds: func(*testing.T, ImageState) ImageState { return baseState },
|
||||
news: func(_ *testing.T, a ImageArgs) ImageArgs {
|
||||
a.Target = "foo"
|
||||
return a
|
||||
},
|
||||
wantChanges: true,
|
||||
},
|
||||
{
|
||||
name: "diff if pulling",
|
||||
olds: func(*testing.T, ImageState) ImageState { return baseState },
|
||||
news: func(_ *testing.T, a ImageArgs) ImageArgs {
|
||||
a.Pull = true
|
||||
return a
|
||||
},
|
||||
wantChanges: true,
|
||||
},
|
||||
{
|
||||
name: "diff if noCache changes",
|
||||
olds: func(*testing.T, ImageState) ImageState { return baseState },
|
||||
news: func(_ *testing.T, a ImageArgs) ImageArgs {
|
||||
a.NoCache = true
|
||||
return a
|
||||
},
|
||||
wantChanges: true,
|
||||
},
|
||||
{
|
||||
name: "diff if labels change",
|
||||
olds: func(*testing.T, ImageState) ImageState { return baseState },
|
||||
news: func(_ *testing.T, a ImageArgs) ImageArgs {
|
||||
a.Labels = map[string]string{"foo": "bar"}
|
||||
return a
|
||||
},
|
||||
wantChanges: true,
|
||||
},
|
||||
{
|
||||
name: "diff if secrets change",
|
||||
olds: func(*testing.T, ImageState) ImageState { return baseState },
|
||||
news: func(_ *testing.T, a ImageArgs) ImageArgs {
|
||||
a.Secrets = map[string]string{"foo": "bar"}
|
||||
return a
|
||||
},
|
||||
wantChanges: true,
|
||||
},
|
||||
}
|
||||
|
||||
s := newServer(nil)
|
||||
|
||||
encode := func(t *testing.T, x any) resource.PropertyMap {
|
||||
raw, err := mapper.New(&mapper.Opts{IgnoreMissing: true}).Encode(x)
|
||||
require.NoError(t, err)
|
||||
return resource.NewPropertyMapFromMap(raw)
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
baseState := baseState
|
||||
baseArgs := baseArgs
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
resp, err := s.Diff(provider.DiffRequest{
|
||||
Urn: _fakeURN,
|
||||
Olds: encode(t, tt.olds(t, baseState)),
|
||||
News: encode(t, tt.news(t, baseArgs)),
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.wantChanges, resp.HasChanges, resp.DetailedDiff)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateImageArgs(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("invalid inputs", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
args := ImageArgs{
|
||||
Tags: []string{"a/bad:tag:format"},
|
||||
Exports: []Export{{Raw: "badexport,-"}},
|
||||
Context: &BuildContext{Context: Context{Location: "./testdata"}},
|
||||
Platforms: []Platform{","},
|
||||
CacheFrom: []CacheFrom{{Raw: "=badcachefrom"}},
|
||||
CacheTo: []CacheTo{{Raw: "=badcacheto"}},
|
||||
}
|
||||
|
||||
_, err := args.validate(false)
|
||||
assert.ErrorContains(t, err, "invalid value badexport")
|
||||
assert.ErrorContains(t, err, "platform specifier component must match")
|
||||
assert.ErrorContains(t, err, "badcachefrom")
|
||||
assert.ErrorContains(t, err, "badcacheto")
|
||||
assert.ErrorContains(t, err, "invalid reference format")
|
||||
assert.ErrorContains(t, err, "testdata/Dockerfile")
|
||||
})
|
||||
|
||||
t.Run("buildOnPreview", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
args := ImageArgs{
|
||||
Context: &BuildContext{Context: Context{Location: "testdata/noop"}},
|
||||
Tags: []string{"my-tag"},
|
||||
Exports: []Export{{Registry: &ExportRegistry{ExportImage{Push: pulumi.BoolRef(true)}}}},
|
||||
}
|
||||
actual, err := args.validate(true)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "image", actual.Exports[0].Type)
|
||||
assert.Equal(t, "false", actual.Exports[0].Attrs["push"])
|
||||
|
||||
actual, err = args.validate(false)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "image", actual.Exports[0].Type)
|
||||
assert.Equal(t, "true", actual.Exports[0].Attrs["push"])
|
||||
})
|
||||
|
||||
t.Run("unknowns", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// pulumi-go-provider gives us zero-values when a property is unknown.
|
||||
// We can't distinguish this from user-provided zero-values, but we
|
||||
// should:
|
||||
// - not fail previews due to these zero values,
|
||||
// - not attempt builds with invalid zero values,
|
||||
// - not allow invalid zero values in non-preview operations.
|
||||
unknowns := ImageArgs{
|
||||
BuildArgs: map[string]string{
|
||||
"known": "value",
|
||||
"": "",
|
||||
},
|
||||
Builder: nil,
|
||||
CacheFrom: []CacheFrom{{GHA: &CacheFromGitHubActions{}}, {Raw: ""}},
|
||||
CacheTo: []CacheTo{{GHA: &CacheToGitHubActions{}}, {Raw: ""}},
|
||||
Context: nil,
|
||||
Exports: []Export{{Raw: ""}},
|
||||
Dockerfile: nil,
|
||||
Platforms: []Platform{"linux/amd64", ""},
|
||||
Registries: []Registry{
|
||||
{
|
||||
Address: "",
|
||||
Password: "",
|
||||
Username: "",
|
||||
},
|
||||
},
|
||||
Tags: []string{"known", ""},
|
||||
}
|
||||
|
||||
_, err := unknowns.validate(true)
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, unknowns.buildable())
|
||||
|
||||
_, err = unknowns.validate(false)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("disabled caches", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
args := ImageArgs{
|
||||
Context: &BuildContext{Context: Context{Location: "testdata/noop"}},
|
||||
CacheFrom: []CacheFrom{{Raw: "type=registry", Disabled: true}},
|
||||
CacheTo: []CacheTo{{Raw: "type=registry", Disabled: true}},
|
||||
Exports: []Export{{Raw: "type=registry", Disabled: true}},
|
||||
}
|
||||
|
||||
opts, err := args.validate(true)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, opts.CacheTo, 0)
|
||||
assert.Len(t, opts.CacheFrom, 0)
|
||||
assert.Len(t, opts.Exports, 0)
|
||||
|
||||
opts, err = args.validate(false)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, opts.CacheTo, 0)
|
||||
assert.Len(t, opts.CacheFrom, 0)
|
||||
assert.Len(t, opts.Exports, 0)
|
||||
})
|
||||
|
||||
t.Run("multiple exports aren't allowed yet", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
args := ImageArgs{
|
||||
Exports: []Export{{Raw: "type=local"}, {Raw: "type=tar"}},
|
||||
}
|
||||
_, err := args.validate(false)
|
||||
assert.ErrorContains(t, err, "multiple exports are currently unsupported")
|
||||
})
|
||||
|
||||
t.Run("cache and export entries are union-ish", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
args := ImageArgs{
|
||||
Exports: []Export{{Tar: &ExportTar{}, Local: &ExportLocal{}}},
|
||||
CacheTo: []CacheTo{{Raw: "type=tar", Local: &CacheToLocal{Dest: "/foo"}}},
|
||||
CacheFrom: []CacheFrom{{Raw: "type=tar", Registry: &CacheFromRegistry{}}},
|
||||
}
|
||||
_, err := args.validate(false)
|
||||
assert.ErrorContains(t, err, "exports should only specify one export type")
|
||||
assert.ErrorContains(t, err, "cacheFrom should only specify one cache type")
|
||||
assert.ErrorContains(t, err, "cacheTo should only specify one cache type")
|
||||
})
|
||||
|
||||
t.Run("dockerfile parsing", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
path := "./testdata/Dockerfile.invalid"
|
||||
data, err := os.ReadFile(path)
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, d := range []Dockerfile{
|
||||
{Location: path}, {Inline: string(data)},
|
||||
} {
|
||||
d := d
|
||||
args := ImageArgs{Dockerfile: &d}
|
||||
_, err := args.validate(false)
|
||||
assert.ErrorContains(t, err, "unknown instruction: RUNN (did you mean RUN?)")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestBuildable(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args ImageArgs
|
||||
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "unknown tags",
|
||||
args: ImageArgs{Tags: []string{""}},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "unknown exports",
|
||||
args: ImageArgs{
|
||||
Tags: []string{"known"},
|
||||
Exports: []Export{{Raw: ""}},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "unknown registry",
|
||||
args: ImageArgs{
|
||||
Tags: []string{"known"},
|
||||
Exports: []Export{{Docker: &ExportDocker{}}},
|
||||
Registries: []Registry{
|
||||
{
|
||||
Address: "docker.io",
|
||||
Username: "foo",
|
||||
Password: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "known tags",
|
||||
args: ImageArgs{
|
||||
Tags: []string{"known"},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "known exports",
|
||||
args: ImageArgs{
|
||||
Tags: []string{"known"},
|
||||
Exports: []Export{{Registry: &ExportRegistry{}}},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "known registry",
|
||||
args: ImageArgs{
|
||||
Tags: []string{"known"},
|
||||
Exports: []Export{{Registry: &ExportRegistry{}}},
|
||||
Registries: []Registry{
|
||||
{
|
||||
Address: "docker.io",
|
||||
Username: "foo",
|
||||
Password: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
actual := tt.args.buildable()
|
||||
assert.Equal(t, tt.want, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestToBuild(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctrl := gomock.NewController(t)
|
||||
pctx := NewMockProviderContext(ctrl)
|
||||
pctx.EXPECT().Log(gomock.Any(), gomock.Any()).AnyTimes()
|
||||
max := Max
|
||||
|
||||
ia := ImageArgs{
|
||||
Tags: []string{"foo", "bar"},
|
||||
Platforms: []Platform{"linux/amd64"},
|
||||
Context: &BuildContext{Context: Context{Location: "testdata/noop"}},
|
||||
CacheTo: []CacheTo{
|
||||
{GHA: &CacheToGitHubActions{CacheWithMode: CacheWithMode{&max}}},
|
||||
{
|
||||
Registry: &CacheToRegistry{
|
||||
CacheFromRegistry: CacheFromRegistry{Ref: "docker.io/foo/bar"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Registry: &CacheToRegistry{
|
||||
CacheFromRegistry: CacheFromRegistry{Ref: "docker.io/foo/bar:baz"},
|
||||
},
|
||||
},
|
||||
},
|
||||
CacheFrom: []CacheFrom{
|
||||
{S3: &CacheFromS3{Name: "bar"}},
|
||||
{Registry: &CacheFromRegistry{Ref: "docker.io/foo/bar"}},
|
||||
{Registry: &CacheFromRegistry{Ref: "docker.io/foo/bar:baz"}},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := ia.toBuild(pctx, false)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
335
provider/internal/index.go
Normal file
335
provider/internal/index.go
Normal file
@@ -0,0 +1,335 @@
|
||||
// Copyright 2024, 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 internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
// For examples/docs.
|
||||
_ "embed"
|
||||
|
||||
provider "github.com/pulumi/pulumi-go-provider"
|
||||
"github.com/pulumi/pulumi-go-provider/infer"
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/common/diag"
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
|
||||
)
|
||||
|
||||
var (
|
||||
_ infer.Annotated = (*Index)(nil)
|
||||
_ infer.Annotated = (*IndexArgs)(nil)
|
||||
_ infer.Annotated = (*IndexState)(nil)
|
||||
_ infer.CustomCheck[IndexArgs] = (*Index)(nil)
|
||||
_ infer.CustomResource[IndexArgs, IndexState] = (*Index)(nil)
|
||||
_ infer.CustomDelete[IndexState] = (*Index)(nil)
|
||||
_ infer.CustomDiff[IndexArgs, IndexState] = (*Index)(nil)
|
||||
_ infer.CustomRead[IndexArgs, IndexState] = (*Index)(nil)
|
||||
_ infer.CustomUpdate[IndexArgs, IndexState] = (*Index)(nil)
|
||||
)
|
||||
|
||||
//go:embed embed/index-examples.md
|
||||
var _indexExamples string
|
||||
|
||||
// Index is an OCI index or manifest list on a remote registry.
|
||||
type Index struct{}
|
||||
|
||||
// IndexArgs instantiate an Index.
|
||||
type IndexArgs struct {
|
||||
Tag string `pulumi:"tag"`
|
||||
Sources []string `pulumi:"sources"`
|
||||
Push bool `pulumi:"push,optional"`
|
||||
Registry *Registry `pulumi:"registry,optional"`
|
||||
}
|
||||
|
||||
// IndexState captures the state of an Index.
|
||||
type IndexState struct {
|
||||
IndexArgs
|
||||
|
||||
Ref string `pulumi:"ref" provider:"output"`
|
||||
}
|
||||
|
||||
// Annotate sets docstrings and defaults on Index.
|
||||
func (i *Index) Annotate(a infer.Annotator) {
|
||||
a.Describe(&i, dedent(`
|
||||
A wrapper around "docker buildx imagetools create" to create an index
|
||||
(or manifest list) referencing one or more existing images.
|
||||
|
||||
In most cases you do not need an "Index" to build a multi-platform
|
||||
image -- specifying multiple platforms on the "Image" will handle this
|
||||
for you automatically.
|
||||
|
||||
However, as of April 2024, building multi-platform images _with
|
||||
caching_ will only export a cache for one platform at a time (see [this
|
||||
discussion](https://github.com/docker/buildx/discussions/1382) for more
|
||||
details).
|
||||
|
||||
Therefore this resource can be helpful if you are building
|
||||
multi-platform images with caching: each platform can be built and
|
||||
cached separately, and an "Index" can join them all together. An
|
||||
example of this is shown below.
|
||||
|
||||
This resource creates an OCI image index or a Docker manifest list
|
||||
depending on the media types of the source images.
|
||||
`)+
|
||||
"\n\n"+_indexExamples,
|
||||
)
|
||||
}
|
||||
|
||||
// Annotate sets docstrings and defaults on IndexArgs.
|
||||
func (i *IndexArgs) Annotate(a infer.Annotator) {
|
||||
a.Describe(&i.Registry, dedent(`
|
||||
Authentication for the registry where the tagged index will be pushed.
|
||||
|
||||
Credentials can also be included with the provider's configuration.
|
||||
`))
|
||||
a.Describe(&i.Sources, dedent(`
|
||||
Existing images to include in the index.
|
||||
`))
|
||||
a.Describe(&i.Tag, dedent(`
|
||||
The tag to apply to the index.
|
||||
`))
|
||||
a.Describe(&i.Push, dedent(`
|
||||
If true, push the index to the target registry.
|
||||
|
||||
Defaults to "true".
|
||||
`))
|
||||
|
||||
a.SetDefault(&i.Push, true)
|
||||
}
|
||||
|
||||
// Annotate sets docstrings on IndexState.
|
||||
func (i *IndexState) Annotate(a infer.Annotator) {
|
||||
a.Describe(&i.Ref, dedent(`
|
||||
The pushed tag with digest.
|
||||
|
||||
Identical to the tag if the index was not pushed.
|
||||
`))
|
||||
}
|
||||
|
||||
// Create is a passthrough to Update.
|
||||
func (i *Index) Create(
|
||||
ctx provider.Context,
|
||||
name string,
|
||||
input IndexArgs,
|
||||
preview bool,
|
||||
) (string, IndexState, error) {
|
||||
state, err := i.Update(ctx, name, IndexState{}, input, preview)
|
||||
return name, state, err
|
||||
}
|
||||
|
||||
// Update performs `buildx imagetools create` to create a new OCI index /
|
||||
// manifest list.
|
||||
func (i *Index) Update(
|
||||
ctx provider.Context,
|
||||
name string,
|
||||
state IndexState,
|
||||
input IndexArgs,
|
||||
preview bool,
|
||||
) (IndexState, error) {
|
||||
state.IndexArgs = input
|
||||
state.Ref = input.Tag
|
||||
|
||||
cli, err := i.client(ctx, state, input)
|
||||
if err != nil {
|
||||
return state, err
|
||||
}
|
||||
|
||||
if preview {
|
||||
return state, nil
|
||||
}
|
||||
|
||||
ctx.Log(diag.Debug, fmt.Sprintf("creating index with tag %s and sources %s", input.Tag, input.Sources))
|
||||
|
||||
err = cli.ManifestCreate(ctx, input.Push, input.Tag, input.Sources...)
|
||||
if err != nil {
|
||||
return state, fmt.Errorf("creating: %w", err)
|
||||
}
|
||||
|
||||
_, _, state, err = i.Read(ctx, name, input, state)
|
||||
if err != nil {
|
||||
return state, fmt.Errorf("reading: %w", err)
|
||||
}
|
||||
return state, nil
|
||||
}
|
||||
|
||||
func (i *Index) Read(
|
||||
ctx provider.Context,
|
||||
name string,
|
||||
input IndexArgs,
|
||||
state IndexState,
|
||||
) (string, IndexArgs, IndexState, error) {
|
||||
state.IndexArgs = input
|
||||
state.Ref = input.Tag
|
||||
|
||||
if !input.Push {
|
||||
ctx.Log(diag.Debug, "skipping read because index was not pushed")
|
||||
return name, input, state, nil // Nothing to read.
|
||||
}
|
||||
|
||||
cli, err := i.client(ctx, state, input)
|
||||
if err != nil {
|
||||
return name, input, state, err
|
||||
}
|
||||
|
||||
ctx.Log(diag.Debug, "reading index with tag "+input.Tag)
|
||||
|
||||
digest, err := cli.ManifestInspect(ctx, input.Tag)
|
||||
if err != nil && strings.Contains(err.Error(), "No such manifest:") && input.Push {
|
||||
// A remote tag was expected but isn't there -- delete the resource.
|
||||
return "", input, state, err
|
||||
}
|
||||
if err != nil && strings.Contains(err.Error(), "No such manifest:") && !input.Push {
|
||||
// Nothing was pushed, so just use the tag without digest..
|
||||
return name, input, state, nil
|
||||
}
|
||||
if err != nil {
|
||||
return name, input, state, err
|
||||
}
|
||||
|
||||
if ref, ok := addDigest(input.Tag, digest); ok {
|
||||
state.Ref = ref
|
||||
}
|
||||
|
||||
return name, input, state, nil
|
||||
}
|
||||
|
||||
// Check confirms the Index's tag and source refs are all valid. This doesn't
|
||||
// fully capture input requirements -- for example buildx requires refs to all
|
||||
// exist on the same registry. This is sufficient to handle the most common
|
||||
// cases for now.
|
||||
func (i *Index) Check(
|
||||
_ provider.Context,
|
||||
_ string,
|
||||
_ resource.PropertyMap,
|
||||
news resource.PropertyMap,
|
||||
) (IndexArgs, []provider.CheckFailure, error) {
|
||||
args, failures, err := infer.DefaultCheck[IndexArgs](news)
|
||||
if err != nil {
|
||||
return args, failures, err
|
||||
}
|
||||
|
||||
if _, err := normalizeReference(args.Tag); args.Tag != "" && err != nil {
|
||||
failures = append(
|
||||
failures,
|
||||
provider.CheckFailure{
|
||||
Property: "target",
|
||||
Reason: err.Error(),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
for idx, s := range args.Sources {
|
||||
if _, err := normalizeReference(s); s != "" && err != nil {
|
||||
failures = append(
|
||||
failures,
|
||||
provider.CheckFailure{
|
||||
Property: fmt.Sprintf("refs[%d]", idx),
|
||||
Reason: err.Error(),
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return args, failures, nil
|
||||
}
|
||||
|
||||
// Delete attempts to delete the remote manifest.
|
||||
func (i *Index) Delete(ctx provider.Context, _ string, state IndexState) error {
|
||||
if !state.Push {
|
||||
return nil // Nothing to delete.
|
||||
}
|
||||
|
||||
cli, err := i.client(ctx, state, state.IndexArgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = cli.ManifestDelete(ctx, state.Ref)
|
||||
// TODO: Upstream buildx swallows the error types we'd like to test for
|
||||
// here.
|
||||
if err != nil && strings.Contains(err.Error(), "No such manifest:") {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Diff returns a diff of proposed changes against current state. Ideally we
|
||||
// wouldn't need to implement all of this, but we currently have to in order to
|
||||
// force `ignoreChanges`-style behavior on our registry password (which can
|
||||
// change all the time due to short-lived AWS credentials).
|
||||
func (i *Index) Diff(
|
||||
_ provider.Context,
|
||||
_ string,
|
||||
olds IndexState,
|
||||
news IndexArgs,
|
||||
) (provider.DiffResponse, error) {
|
||||
diff := map[string]provider.PropertyDiff{}
|
||||
update := provider.PropertyDiff{Kind: provider.Update}
|
||||
replace := provider.PropertyDiff{Kind: provider.UpdateReplace}
|
||||
|
||||
if olds.Tag != news.Tag {
|
||||
diff["tag"] = replace
|
||||
}
|
||||
if !reflect.DeepEqual(olds.Sources, news.Sources) {
|
||||
diff["sources"] = update
|
||||
}
|
||||
if olds.Registry != nil && news.Registry != nil {
|
||||
if olds.Registry.Address != news.Registry.Address {
|
||||
diff["registry.address"] = update
|
||||
if olds.Registry.Address != "" {
|
||||
diff["registry.address"] = replace
|
||||
}
|
||||
}
|
||||
if olds.Registry.Username != news.Registry.Username {
|
||||
diff["registry.username"] = update
|
||||
}
|
||||
}
|
||||
if (olds.Registry == nil && news.Registry != nil) ||
|
||||
(olds.Registry != nil && news.Registry == nil) {
|
||||
diff["registry"] = update
|
||||
}
|
||||
// Intentionally ignore changes to registry.password
|
||||
|
||||
return provider.DiffResponse{
|
||||
HasChanges: len(diff) > 0,
|
||||
DetailedDiff: diff,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// client produces a CLI client with scoped to this resource and layered on top
|
||||
// of any host-level credentials.
|
||||
func (i *Index) client(
|
||||
ctx provider.Context,
|
||||
_ IndexState,
|
||||
args IndexArgs,
|
||||
) (Client, error) {
|
||||
cfg := infer.GetConfig[Config](ctx)
|
||||
|
||||
if cli, ok := ctx.Value(_mockClientKey).(Client); ok {
|
||||
return cli, nil
|
||||
}
|
||||
|
||||
// We prefer auth from args, the provider, and state in that order. We
|
||||
// build a slice in reverse order because wrap() will overwrite earlier
|
||||
// entries with later ones.
|
||||
auths := []Registry{}
|
||||
auths = append(auths, cfg.Registries...)
|
||||
if args.Registry != nil {
|
||||
auths = append(auths, *args.Registry)
|
||||
}
|
||||
|
||||
return wrap(cfg.host, auths...)
|
||||
}
|
||||
210
provider/internal/index_test.go
Normal file
210
provider/internal/index_test.go
Normal file
@@ -0,0 +1,210 @@
|
||||
// Copyright 2024, 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 internal
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
provider "github.com/pulumi/pulumi-go-provider"
|
||||
"github.com/pulumi/pulumi-go-provider/integration"
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/common/util/mapper"
|
||||
)
|
||||
|
||||
func TestIndexLifecycle(t *testing.T) {
|
||||
t.Parallel()
|
||||
realClient := func(t *testing.T) Client { return nil }
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
skip bool
|
||||
|
||||
op func(t *testing.T) integration.Operation
|
||||
client func(t *testing.T) Client
|
||||
}{
|
||||
{
|
||||
name: "not pushed",
|
||||
client: realClient,
|
||||
op: func(t *testing.T) integration.Operation {
|
||||
return integration.Operation{
|
||||
Inputs: resource.PropertyMap{
|
||||
"tag": resource.NewStringProperty(
|
||||
"docker.io/pulumibot/buildkit-e2e:manifest-unit",
|
||||
),
|
||||
"sources": resource.NewArrayProperty([]resource.PropertyValue{
|
||||
resource.NewStringProperty("docker.io/pulumibot/buildkit-e2e:arm64"),
|
||||
resource.NewStringProperty("docker.io/pulumibot/buildkit-e2e:amd64"),
|
||||
}),
|
||||
"push": resource.NewBoolProperty(false),
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "pushed",
|
||||
skip: os.Getenv("DOCKER_HUB_PASSWORD") == "",
|
||||
client: realClient,
|
||||
op: func(t *testing.T) integration.Operation {
|
||||
return integration.Operation{
|
||||
Inputs: resource.PropertyMap{
|
||||
"tag": resource.NewStringProperty(
|
||||
"docker.io/pulumibot/buildkit-e2e:manifest",
|
||||
),
|
||||
"sources": resource.NewArrayProperty([]resource.PropertyValue{
|
||||
resource.NewStringProperty("docker.io/pulumibot/buildkit-e2e:arm64"),
|
||||
resource.NewStringProperty("docker.io/pulumibot/buildkit-e2e:amd64"),
|
||||
}),
|
||||
"push": resource.NewBoolProperty(true),
|
||||
"registry": resource.NewObjectProperty(resource.PropertyMap{
|
||||
"address": resource.NewStringProperty("docker.io"),
|
||||
"username": resource.NewStringProperty("pulumibot"),
|
||||
"password": resource.NewSecretProperty(&resource.Secret{
|
||||
Element: resource.NewStringProperty(
|
||||
os.Getenv("DOCKER_HUB_PASSWORD"),
|
||||
),
|
||||
}),
|
||||
}),
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if tt.skip {
|
||||
t.Skip("missing environment variables")
|
||||
}
|
||||
lc := integration.LifeCycleTest{
|
||||
Resource: "docker-build:index:Index",
|
||||
Create: tt.op(t),
|
||||
}
|
||||
s := newServer(tt.client(t))
|
||||
|
||||
err := s.Configure(provider.ConfigureRequest{})
|
||||
require.NoError(t, err)
|
||||
|
||||
lc.Run(t, s)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIndexDiff(t *testing.T) {
|
||||
t.Parallel()
|
||||
urn := resource.NewURN("test", "provider", "a", "docker-build:index:Index", "test")
|
||||
baseArgs := IndexArgs{Sources: []string{"docker.io/nginx:latest"}}
|
||||
baseState := IndexState{IndexArgs: baseArgs}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
olds func(*testing.T, IndexState) IndexState
|
||||
news func(*testing.T, IndexArgs) IndexArgs
|
||||
|
||||
wantChanges bool
|
||||
}{
|
||||
{
|
||||
name: "no diff if no changes",
|
||||
olds: func(*testing.T, IndexState) IndexState { return baseState },
|
||||
news: func(*testing.T, IndexArgs) IndexArgs { return baseArgs },
|
||||
wantChanges: false,
|
||||
},
|
||||
{
|
||||
name: "diff if tag changes",
|
||||
olds: func(*testing.T, IndexState) IndexState { return baseState },
|
||||
news: func(t *testing.T, a IndexArgs) IndexArgs {
|
||||
a.Tag = "new-tag"
|
||||
return a
|
||||
},
|
||||
wantChanges: true,
|
||||
},
|
||||
{
|
||||
name: "no diff if registry password changes",
|
||||
olds: func(_ *testing.T, s IndexState) IndexState {
|
||||
s.Registry = &Registry{
|
||||
Address: "foo",
|
||||
Username: "foo",
|
||||
Password: "foo",
|
||||
}
|
||||
return s
|
||||
},
|
||||
news: func(_ *testing.T, a IndexArgs) IndexArgs {
|
||||
a.Registry = &Registry{
|
||||
Address: "foo",
|
||||
Username: "foo",
|
||||
Password: "DIFFERENT PASSWORD",
|
||||
}
|
||||
return a
|
||||
},
|
||||
wantChanges: false,
|
||||
},
|
||||
{
|
||||
name: "diff if registry added",
|
||||
olds: func(*testing.T, IndexState) IndexState { return baseState },
|
||||
news: func(_ *testing.T, a IndexArgs) IndexArgs {
|
||||
a.Registry = &Registry{Address: "foo.com", Username: "foo", Password: "foo"}
|
||||
return a
|
||||
},
|
||||
wantChanges: true,
|
||||
},
|
||||
{
|
||||
name: "diff if registry user changes",
|
||||
olds: func(_ *testing.T, s IndexState) IndexState {
|
||||
s.Registry = &Registry{
|
||||
Address: "foo",
|
||||
Username: "foo",
|
||||
Password: "foo",
|
||||
}
|
||||
return s
|
||||
},
|
||||
news: func(_ *testing.T, a IndexArgs) IndexArgs {
|
||||
a.Registry = &Registry{
|
||||
Address: "DIFFERENT USER",
|
||||
Username: "foo",
|
||||
Password: "foo",
|
||||
}
|
||||
return a
|
||||
},
|
||||
wantChanges: true,
|
||||
},
|
||||
}
|
||||
|
||||
s := newServer(nil)
|
||||
|
||||
encode := func(t *testing.T, x any) resource.PropertyMap {
|
||||
raw, err := mapper.New(&mapper.Opts{IgnoreMissing: true}).Encode(x)
|
||||
require.NoError(t, err)
|
||||
return resource.NewPropertyMapFromMap(raw)
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
resp, err := s.Diff(provider.DiffRequest{
|
||||
Urn: urn,
|
||||
Olds: encode(t, tt.olds(t, baseState)),
|
||||
News: encode(t, tt.news(t, baseArgs)),
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.wantChanges, resp.HasChanges, resp.DetailedDiff)
|
||||
})
|
||||
}
|
||||
}
|
||||
1
provider/internal/metadata.json
Normal file
1
provider/internal/metadata.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
737
provider/internal/mockcli_test.go
generated
Normal file
737
provider/internal/mockcli_test.go
generated
Normal file
@@ -0,0 +1,737 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: cli.go
|
||||
//
|
||||
// Generated by this command:
|
||||
//
|
||||
// mockgen -typed -package internal -source cli.go -destination mockcli_test.go --self_package github.com/pulumi/pulumi-docker-build/provider/internal
|
||||
//
|
||||
// Package internal is a generated GoMock package.
|
||||
package internal
|
||||
|
||||
import (
|
||||
io "io"
|
||||
reflect "reflect"
|
||||
|
||||
command "github.com/docker/cli/cli/command"
|
||||
configfile "github.com/docker/cli/cli/config/configfile"
|
||||
docker "github.com/docker/cli/cli/context/docker"
|
||||
store "github.com/docker/cli/cli/context/store"
|
||||
store0 "github.com/docker/cli/cli/manifest/store"
|
||||
client "github.com/docker/cli/cli/registry/client"
|
||||
streams "github.com/docker/cli/cli/streams"
|
||||
trust "github.com/docker/cli/cli/trust"
|
||||
client0 "github.com/docker/docker/client"
|
||||
client1 "github.com/theupdateframework/notary/client"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
// MockCli is a mock of Cli interface.
|
||||
type MockCli struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockCliMockRecorder
|
||||
}
|
||||
|
||||
// MockCliMockRecorder is the mock recorder for MockCli.
|
||||
type MockCliMockRecorder struct {
|
||||
mock *MockCli
|
||||
}
|
||||
|
||||
// NewMockCli creates a new mock instance.
|
||||
func NewMockCli(ctrl *gomock.Controller) *MockCli {
|
||||
mock := &MockCli{ctrl: ctrl}
|
||||
mock.recorder = &MockCliMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockCli) EXPECT() *MockCliMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Apply mocks base method.
|
||||
func (m *MockCli) Apply(ops ...command.CLIOption) error {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []any{}
|
||||
for _, a := range ops {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "Apply", varargs...)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Apply indicates an expected call of Apply.
|
||||
func (mr *MockCliMockRecorder) Apply(ops ...any) *CliApplyCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Apply", reflect.TypeOf((*MockCli)(nil).Apply), ops...)
|
||||
return &CliApplyCall{Call: call}
|
||||
}
|
||||
|
||||
// CliApplyCall wrap *gomock.Call
|
||||
type CliApplyCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *CliApplyCall) Return(arg0 error) *CliApplyCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *CliApplyCall) Do(f func(...command.CLIOption) error) *CliApplyCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *CliApplyCall) DoAndReturn(f func(...command.CLIOption) error) *CliApplyCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// BuildKitEnabled mocks base method.
|
||||
func (m *MockCli) BuildKitEnabled() (bool, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "BuildKitEnabled")
|
||||
ret0, _ := ret[0].(bool)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// BuildKitEnabled indicates an expected call of BuildKitEnabled.
|
||||
func (mr *MockCliMockRecorder) BuildKitEnabled() *CliBuildKitEnabledCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BuildKitEnabled", reflect.TypeOf((*MockCli)(nil).BuildKitEnabled))
|
||||
return &CliBuildKitEnabledCall{Call: call}
|
||||
}
|
||||
|
||||
// CliBuildKitEnabledCall wrap *gomock.Call
|
||||
type CliBuildKitEnabledCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *CliBuildKitEnabledCall) Return(arg0 bool, arg1 error) *CliBuildKitEnabledCall {
|
||||
c.Call = c.Call.Return(arg0, arg1)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *CliBuildKitEnabledCall) Do(f func() (bool, error)) *CliBuildKitEnabledCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *CliBuildKitEnabledCall) DoAndReturn(f func() (bool, error)) *CliBuildKitEnabledCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// Client mocks base method.
|
||||
func (m *MockCli) Client() client0.APIClient {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Client")
|
||||
ret0, _ := ret[0].(client0.APIClient)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Client indicates an expected call of Client.
|
||||
func (mr *MockCliMockRecorder) Client() *CliClientCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Client", reflect.TypeOf((*MockCli)(nil).Client))
|
||||
return &CliClientCall{Call: call}
|
||||
}
|
||||
|
||||
// CliClientCall wrap *gomock.Call
|
||||
type CliClientCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *CliClientCall) Return(arg0 client0.APIClient) *CliClientCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *CliClientCall) Do(f func() client0.APIClient) *CliClientCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *CliClientCall) DoAndReturn(f func() client0.APIClient) *CliClientCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// ConfigFile mocks base method.
|
||||
func (m *MockCli) ConfigFile() *configfile.ConfigFile {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "ConfigFile")
|
||||
ret0, _ := ret[0].(*configfile.ConfigFile)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// ConfigFile indicates an expected call of ConfigFile.
|
||||
func (mr *MockCliMockRecorder) ConfigFile() *CliConfigFileCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ConfigFile", reflect.TypeOf((*MockCli)(nil).ConfigFile))
|
||||
return &CliConfigFileCall{Call: call}
|
||||
}
|
||||
|
||||
// CliConfigFileCall wrap *gomock.Call
|
||||
type CliConfigFileCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *CliConfigFileCall) Return(arg0 *configfile.ConfigFile) *CliConfigFileCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *CliConfigFileCall) Do(f func() *configfile.ConfigFile) *CliConfigFileCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *CliConfigFileCall) DoAndReturn(f func() *configfile.ConfigFile) *CliConfigFileCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// ContentTrustEnabled mocks base method.
|
||||
func (m *MockCli) ContentTrustEnabled() bool {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "ContentTrustEnabled")
|
||||
ret0, _ := ret[0].(bool)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// ContentTrustEnabled indicates an expected call of ContentTrustEnabled.
|
||||
func (mr *MockCliMockRecorder) ContentTrustEnabled() *CliContentTrustEnabledCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ContentTrustEnabled", reflect.TypeOf((*MockCli)(nil).ContentTrustEnabled))
|
||||
return &CliContentTrustEnabledCall{Call: call}
|
||||
}
|
||||
|
||||
// CliContentTrustEnabledCall wrap *gomock.Call
|
||||
type CliContentTrustEnabledCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *CliContentTrustEnabledCall) Return(arg0 bool) *CliContentTrustEnabledCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *CliContentTrustEnabledCall) Do(f func() bool) *CliContentTrustEnabledCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *CliContentTrustEnabledCall) DoAndReturn(f func() bool) *CliContentTrustEnabledCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// ContextStore mocks base method.
|
||||
func (m *MockCli) ContextStore() store.Store {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "ContextStore")
|
||||
ret0, _ := ret[0].(store.Store)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// ContextStore indicates an expected call of ContextStore.
|
||||
func (mr *MockCliMockRecorder) ContextStore() *CliContextStoreCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ContextStore", reflect.TypeOf((*MockCli)(nil).ContextStore))
|
||||
return &CliContextStoreCall{Call: call}
|
||||
}
|
||||
|
||||
// CliContextStoreCall wrap *gomock.Call
|
||||
type CliContextStoreCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *CliContextStoreCall) Return(arg0 store.Store) *CliContextStoreCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *CliContextStoreCall) Do(f func() store.Store) *CliContextStoreCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *CliContextStoreCall) DoAndReturn(f func() store.Store) *CliContextStoreCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// CurrentContext mocks base method.
|
||||
func (m *MockCli) CurrentContext() string {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "CurrentContext")
|
||||
ret0, _ := ret[0].(string)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// CurrentContext indicates an expected call of CurrentContext.
|
||||
func (mr *MockCliMockRecorder) CurrentContext() *CliCurrentContextCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CurrentContext", reflect.TypeOf((*MockCli)(nil).CurrentContext))
|
||||
return &CliCurrentContextCall{Call: call}
|
||||
}
|
||||
|
||||
// CliCurrentContextCall wrap *gomock.Call
|
||||
type CliCurrentContextCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *CliCurrentContextCall) Return(arg0 string) *CliCurrentContextCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *CliCurrentContextCall) Do(f func() string) *CliCurrentContextCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *CliCurrentContextCall) DoAndReturn(f func() string) *CliCurrentContextCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// CurrentVersion mocks base method.
|
||||
func (m *MockCli) CurrentVersion() string {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "CurrentVersion")
|
||||
ret0, _ := ret[0].(string)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// CurrentVersion indicates an expected call of CurrentVersion.
|
||||
func (mr *MockCliMockRecorder) CurrentVersion() *CliCurrentVersionCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CurrentVersion", reflect.TypeOf((*MockCli)(nil).CurrentVersion))
|
||||
return &CliCurrentVersionCall{Call: call}
|
||||
}
|
||||
|
||||
// CliCurrentVersionCall wrap *gomock.Call
|
||||
type CliCurrentVersionCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *CliCurrentVersionCall) Return(arg0 string) *CliCurrentVersionCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *CliCurrentVersionCall) Do(f func() string) *CliCurrentVersionCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *CliCurrentVersionCall) DoAndReturn(f func() string) *CliCurrentVersionCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DefaultVersion mocks base method.
|
||||
func (m *MockCli) DefaultVersion() string {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "DefaultVersion")
|
||||
ret0, _ := ret[0].(string)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// DefaultVersion indicates an expected call of DefaultVersion.
|
||||
func (mr *MockCliMockRecorder) DefaultVersion() *CliDefaultVersionCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DefaultVersion", reflect.TypeOf((*MockCli)(nil).DefaultVersion))
|
||||
return &CliDefaultVersionCall{Call: call}
|
||||
}
|
||||
|
||||
// CliDefaultVersionCall wrap *gomock.Call
|
||||
type CliDefaultVersionCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *CliDefaultVersionCall) Return(arg0 string) *CliDefaultVersionCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *CliDefaultVersionCall) Do(f func() string) *CliDefaultVersionCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *CliDefaultVersionCall) DoAndReturn(f func() string) *CliDefaultVersionCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DockerEndpoint mocks base method.
|
||||
func (m *MockCli) DockerEndpoint() docker.Endpoint {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "DockerEndpoint")
|
||||
ret0, _ := ret[0].(docker.Endpoint)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// DockerEndpoint indicates an expected call of DockerEndpoint.
|
||||
func (mr *MockCliMockRecorder) DockerEndpoint() *CliDockerEndpointCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DockerEndpoint", reflect.TypeOf((*MockCli)(nil).DockerEndpoint))
|
||||
return &CliDockerEndpointCall{Call: call}
|
||||
}
|
||||
|
||||
// CliDockerEndpointCall wrap *gomock.Call
|
||||
type CliDockerEndpointCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *CliDockerEndpointCall) Return(arg0 docker.Endpoint) *CliDockerEndpointCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *CliDockerEndpointCall) Do(f func() docker.Endpoint) *CliDockerEndpointCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *CliDockerEndpointCall) DoAndReturn(f func() docker.Endpoint) *CliDockerEndpointCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// Err mocks base method.
|
||||
func (m *MockCli) Err() io.Writer {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Err")
|
||||
ret0, _ := ret[0].(io.Writer)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Err indicates an expected call of Err.
|
||||
func (mr *MockCliMockRecorder) Err() *CliErrCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Err", reflect.TypeOf((*MockCli)(nil).Err))
|
||||
return &CliErrCall{Call: call}
|
||||
}
|
||||
|
||||
// CliErrCall wrap *gomock.Call
|
||||
type CliErrCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *CliErrCall) Return(arg0 io.Writer) *CliErrCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *CliErrCall) Do(f func() io.Writer) *CliErrCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *CliErrCall) DoAndReturn(f func() io.Writer) *CliErrCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// In mocks base method.
|
||||
func (m *MockCli) In() *streams.In {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "In")
|
||||
ret0, _ := ret[0].(*streams.In)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// In indicates an expected call of In.
|
||||
func (mr *MockCliMockRecorder) In() *CliInCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "In", reflect.TypeOf((*MockCli)(nil).In))
|
||||
return &CliInCall{Call: call}
|
||||
}
|
||||
|
||||
// CliInCall wrap *gomock.Call
|
||||
type CliInCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *CliInCall) Return(arg0 *streams.In) *CliInCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *CliInCall) Do(f func() *streams.In) *CliInCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *CliInCall) DoAndReturn(f func() *streams.In) *CliInCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// ManifestStore mocks base method.
|
||||
func (m *MockCli) ManifestStore() store0.Store {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "ManifestStore")
|
||||
ret0, _ := ret[0].(store0.Store)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// ManifestStore indicates an expected call of ManifestStore.
|
||||
func (mr *MockCliMockRecorder) ManifestStore() *CliManifestStoreCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ManifestStore", reflect.TypeOf((*MockCli)(nil).ManifestStore))
|
||||
return &CliManifestStoreCall{Call: call}
|
||||
}
|
||||
|
||||
// CliManifestStoreCall wrap *gomock.Call
|
||||
type CliManifestStoreCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *CliManifestStoreCall) Return(arg0 store0.Store) *CliManifestStoreCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *CliManifestStoreCall) Do(f func() store0.Store) *CliManifestStoreCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *CliManifestStoreCall) DoAndReturn(f func() store0.Store) *CliManifestStoreCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// NotaryClient mocks base method.
|
||||
func (m *MockCli) NotaryClient(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (client1.Repository, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "NotaryClient", imgRefAndAuth, actions)
|
||||
ret0, _ := ret[0].(client1.Repository)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// NotaryClient indicates an expected call of NotaryClient.
|
||||
func (mr *MockCliMockRecorder) NotaryClient(imgRefAndAuth, actions any) *CliNotaryClientCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NotaryClient", reflect.TypeOf((*MockCli)(nil).NotaryClient), imgRefAndAuth, actions)
|
||||
return &CliNotaryClientCall{Call: call}
|
||||
}
|
||||
|
||||
// CliNotaryClientCall wrap *gomock.Call
|
||||
type CliNotaryClientCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *CliNotaryClientCall) Return(arg0 client1.Repository, arg1 error) *CliNotaryClientCall {
|
||||
c.Call = c.Call.Return(arg0, arg1)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *CliNotaryClientCall) Do(f func(trust.ImageRefAndAuth, []string) (client1.Repository, error)) *CliNotaryClientCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *CliNotaryClientCall) DoAndReturn(f func(trust.ImageRefAndAuth, []string) (client1.Repository, error)) *CliNotaryClientCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// Out mocks base method.
|
||||
func (m *MockCli) Out() *streams.Out {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Out")
|
||||
ret0, _ := ret[0].(*streams.Out)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Out indicates an expected call of Out.
|
||||
func (mr *MockCliMockRecorder) Out() *CliOutCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Out", reflect.TypeOf((*MockCli)(nil).Out))
|
||||
return &CliOutCall{Call: call}
|
||||
}
|
||||
|
||||
// CliOutCall wrap *gomock.Call
|
||||
type CliOutCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *CliOutCall) Return(arg0 *streams.Out) *CliOutCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *CliOutCall) Do(f func() *streams.Out) *CliOutCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *CliOutCall) DoAndReturn(f func() *streams.Out) *CliOutCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// RegistryClient mocks base method.
|
||||
func (m *MockCli) RegistryClient(arg0 bool) client.RegistryClient {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "RegistryClient", arg0)
|
||||
ret0, _ := ret[0].(client.RegistryClient)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// RegistryClient indicates an expected call of RegistryClient.
|
||||
func (mr *MockCliMockRecorder) RegistryClient(arg0 any) *CliRegistryClientCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RegistryClient", reflect.TypeOf((*MockCli)(nil).RegistryClient), arg0)
|
||||
return &CliRegistryClientCall{Call: call}
|
||||
}
|
||||
|
||||
// CliRegistryClientCall wrap *gomock.Call
|
||||
type CliRegistryClientCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *CliRegistryClientCall) Return(arg0 client.RegistryClient) *CliRegistryClientCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *CliRegistryClientCall) Do(f func(bool) client.RegistryClient) *CliRegistryClientCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *CliRegistryClientCall) DoAndReturn(f func(bool) client.RegistryClient) *CliRegistryClientCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// ServerInfo mocks base method.
|
||||
func (m *MockCli) ServerInfo() command.ServerInfo {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "ServerInfo")
|
||||
ret0, _ := ret[0].(command.ServerInfo)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// ServerInfo indicates an expected call of ServerInfo.
|
||||
func (mr *MockCliMockRecorder) ServerInfo() *CliServerInfoCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ServerInfo", reflect.TypeOf((*MockCli)(nil).ServerInfo))
|
||||
return &CliServerInfoCall{Call: call}
|
||||
}
|
||||
|
||||
// CliServerInfoCall wrap *gomock.Call
|
||||
type CliServerInfoCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *CliServerInfoCall) Return(arg0 command.ServerInfo) *CliServerInfoCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *CliServerInfoCall) Do(f func() command.ServerInfo) *CliServerInfoCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *CliServerInfoCall) DoAndReturn(f func() command.ServerInfo) *CliServerInfoCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// SetIn mocks base method.
|
||||
func (m *MockCli) SetIn(in *streams.In) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "SetIn", in)
|
||||
}
|
||||
|
||||
// SetIn indicates an expected call of SetIn.
|
||||
func (mr *MockCliMockRecorder) SetIn(in any) *CliSetInCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetIn", reflect.TypeOf((*MockCli)(nil).SetIn), in)
|
||||
return &CliSetInCall{Call: call}
|
||||
}
|
||||
|
||||
// CliSetInCall wrap *gomock.Call
|
||||
type CliSetInCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *CliSetInCall) Return() *CliSetInCall {
|
||||
c.Call = c.Call.Return()
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *CliSetInCall) Do(f func(*streams.In)) *CliSetInCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *CliSetInCall) DoAndReturn(f func(*streams.In)) *CliSetInCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
494
provider/internal/mockclient_test.go
generated
Normal file
494
provider/internal/mockclient_test.go
generated
Normal file
@@ -0,0 +1,494 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: client.go
|
||||
//
|
||||
// Generated by this command:
|
||||
//
|
||||
// mockgen -typed -package internal -source client.go -destination mockclient_test.go --self_package github.com/pulumi/pulumi-docker-build/provider/internal
|
||||
//
|
||||
// Package internal is a generated GoMock package.
|
||||
package internal
|
||||
|
||||
import (
|
||||
context "context"
|
||||
reflect "reflect"
|
||||
|
||||
pb "github.com/docker/buildx/controller/pb"
|
||||
client "github.com/moby/buildkit/client"
|
||||
session "github.com/moby/buildkit/session"
|
||||
provider "github.com/pulumi/pulumi-go-provider"
|
||||
descriptor "github.com/regclient/regclient/types/descriptor"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
// MockClient is a mock of Client interface.
|
||||
type MockClient struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockClientMockRecorder
|
||||
}
|
||||
|
||||
// MockClientMockRecorder is the mock recorder for MockClient.
|
||||
type MockClientMockRecorder struct {
|
||||
mock *MockClient
|
||||
}
|
||||
|
||||
// NewMockClient creates a new mock instance.
|
||||
func NewMockClient(ctrl *gomock.Controller) *MockClient {
|
||||
mock := &MockClient{ctrl: ctrl}
|
||||
mock.recorder = &MockClientMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockClient) EXPECT() *MockClientMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Build mocks base method.
|
||||
func (m *MockClient) Build(ctx provider.Context, b Build) (*client.SolveResponse, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Build", ctx, b)
|
||||
ret0, _ := ret[0].(*client.SolveResponse)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// Build indicates an expected call of Build.
|
||||
func (mr *MockClientMockRecorder) Build(ctx, b any) *ClientBuildCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Build", reflect.TypeOf((*MockClient)(nil).Build), ctx, b)
|
||||
return &ClientBuildCall{Call: call}
|
||||
}
|
||||
|
||||
// ClientBuildCall wrap *gomock.Call
|
||||
type ClientBuildCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *ClientBuildCall) Return(arg0 *client.SolveResponse, arg1 error) *ClientBuildCall {
|
||||
c.Call = c.Call.Return(arg0, arg1)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *ClientBuildCall) Do(f func(provider.Context, Build) (*client.SolveResponse, error)) *ClientBuildCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *ClientBuildCall) DoAndReturn(f func(provider.Context, Build) (*client.SolveResponse, error)) *ClientBuildCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// BuildKitEnabled mocks base method.
|
||||
func (m *MockClient) BuildKitEnabled() (bool, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "BuildKitEnabled")
|
||||
ret0, _ := ret[0].(bool)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// BuildKitEnabled indicates an expected call of BuildKitEnabled.
|
||||
func (mr *MockClientMockRecorder) BuildKitEnabled() *ClientBuildKitEnabledCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BuildKitEnabled", reflect.TypeOf((*MockClient)(nil).BuildKitEnabled))
|
||||
return &ClientBuildKitEnabledCall{Call: call}
|
||||
}
|
||||
|
||||
// ClientBuildKitEnabledCall wrap *gomock.Call
|
||||
type ClientBuildKitEnabledCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *ClientBuildKitEnabledCall) Return(arg0 bool, arg1 error) *ClientBuildKitEnabledCall {
|
||||
c.Call = c.Call.Return(arg0, arg1)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *ClientBuildKitEnabledCall) Do(f func() (bool, error)) *ClientBuildKitEnabledCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *ClientBuildKitEnabledCall) DoAndReturn(f func() (bool, error)) *ClientBuildKitEnabledCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// Delete mocks base method.
|
||||
func (m *MockClient) Delete(ctx context.Context, id string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Delete", ctx, id)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Delete indicates an expected call of Delete.
|
||||
func (mr *MockClientMockRecorder) Delete(ctx, id any) *ClientDeleteCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockClient)(nil).Delete), ctx, id)
|
||||
return &ClientDeleteCall{Call: call}
|
||||
}
|
||||
|
||||
// ClientDeleteCall wrap *gomock.Call
|
||||
type ClientDeleteCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *ClientDeleteCall) Return(arg0 error) *ClientDeleteCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *ClientDeleteCall) Do(f func(context.Context, string) error) *ClientDeleteCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *ClientDeleteCall) DoAndReturn(f func(context.Context, string) error) *ClientDeleteCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// Inspect mocks base method.
|
||||
func (m *MockClient) Inspect(ctx context.Context, id string) ([]descriptor.Descriptor, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Inspect", ctx, id)
|
||||
ret0, _ := ret[0].([]descriptor.Descriptor)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// Inspect indicates an expected call of Inspect.
|
||||
func (mr *MockClientMockRecorder) Inspect(ctx, id any) *ClientInspectCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Inspect", reflect.TypeOf((*MockClient)(nil).Inspect), ctx, id)
|
||||
return &ClientInspectCall{Call: call}
|
||||
}
|
||||
|
||||
// ClientInspectCall wrap *gomock.Call
|
||||
type ClientInspectCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *ClientInspectCall) Return(arg0 []descriptor.Descriptor, arg1 error) *ClientInspectCall {
|
||||
c.Call = c.Call.Return(arg0, arg1)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *ClientInspectCall) Do(f func(context.Context, string) ([]descriptor.Descriptor, error)) *ClientInspectCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *ClientInspectCall) DoAndReturn(f func(context.Context, string) ([]descriptor.Descriptor, error)) *ClientInspectCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// ManifestCreate mocks base method.
|
||||
func (m *MockClient) ManifestCreate(ctx provider.Context, push bool, target string, refs ...string) error {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []any{ctx, push, target}
|
||||
for _, a := range refs {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "ManifestCreate", varargs...)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// ManifestCreate indicates an expected call of ManifestCreate.
|
||||
func (mr *MockClientMockRecorder) ManifestCreate(ctx, push, target any, refs ...any) *ClientManifestCreateCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]any{ctx, push, target}, refs...)
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ManifestCreate", reflect.TypeOf((*MockClient)(nil).ManifestCreate), varargs...)
|
||||
return &ClientManifestCreateCall{Call: call}
|
||||
}
|
||||
|
||||
// ClientManifestCreateCall wrap *gomock.Call
|
||||
type ClientManifestCreateCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *ClientManifestCreateCall) Return(arg0 error) *ClientManifestCreateCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *ClientManifestCreateCall) Do(f func(provider.Context, bool, string, ...string) error) *ClientManifestCreateCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *ClientManifestCreateCall) DoAndReturn(f func(provider.Context, bool, string, ...string) error) *ClientManifestCreateCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// ManifestDelete mocks base method.
|
||||
func (m *MockClient) ManifestDelete(ctx provider.Context, target string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "ManifestDelete", ctx, target)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// ManifestDelete indicates an expected call of ManifestDelete.
|
||||
func (mr *MockClientMockRecorder) ManifestDelete(ctx, target any) *ClientManifestDeleteCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ManifestDelete", reflect.TypeOf((*MockClient)(nil).ManifestDelete), ctx, target)
|
||||
return &ClientManifestDeleteCall{Call: call}
|
||||
}
|
||||
|
||||
// ClientManifestDeleteCall wrap *gomock.Call
|
||||
type ClientManifestDeleteCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *ClientManifestDeleteCall) Return(arg0 error) *ClientManifestDeleteCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *ClientManifestDeleteCall) Do(f func(provider.Context, string) error) *ClientManifestDeleteCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *ClientManifestDeleteCall) DoAndReturn(f func(provider.Context, string) error) *ClientManifestDeleteCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// ManifestInspect mocks base method.
|
||||
func (m *MockClient) ManifestInspect(ctx provider.Context, target string) (string, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "ManifestInspect", ctx, target)
|
||||
ret0, _ := ret[0].(string)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// ManifestInspect indicates an expected call of ManifestInspect.
|
||||
func (mr *MockClientMockRecorder) ManifestInspect(ctx, target any) *ClientManifestInspectCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ManifestInspect", reflect.TypeOf((*MockClient)(nil).ManifestInspect), ctx, target)
|
||||
return &ClientManifestInspectCall{Call: call}
|
||||
}
|
||||
|
||||
// ClientManifestInspectCall wrap *gomock.Call
|
||||
type ClientManifestInspectCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *ClientManifestInspectCall) Return(arg0 string, arg1 error) *ClientManifestInspectCall {
|
||||
c.Call = c.Call.Return(arg0, arg1)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *ClientManifestInspectCall) Do(f func(provider.Context, string) (string, error)) *ClientManifestInspectCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *ClientManifestInspectCall) DoAndReturn(f func(provider.Context, string) (string, error)) *ClientManifestInspectCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// MockBuild is a mock of Build interface.
|
||||
type MockBuild struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockBuildMockRecorder
|
||||
}
|
||||
|
||||
// MockBuildMockRecorder is the mock recorder for MockBuild.
|
||||
type MockBuildMockRecorder struct {
|
||||
mock *MockBuild
|
||||
}
|
||||
|
||||
// NewMockBuild creates a new mock instance.
|
||||
func NewMockBuild(ctrl *gomock.Controller) *MockBuild {
|
||||
mock := &MockBuild{ctrl: ctrl}
|
||||
mock.recorder = &MockBuildMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockBuild) EXPECT() *MockBuildMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// BuildOptions mocks base method.
|
||||
func (m *MockBuild) BuildOptions() pb.BuildOptions {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "BuildOptions")
|
||||
ret0, _ := ret[0].(pb.BuildOptions)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// BuildOptions indicates an expected call of BuildOptions.
|
||||
func (mr *MockBuildMockRecorder) BuildOptions() *BuildBuildOptionsCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BuildOptions", reflect.TypeOf((*MockBuild)(nil).BuildOptions))
|
||||
return &BuildBuildOptionsCall{Call: call}
|
||||
}
|
||||
|
||||
// BuildBuildOptionsCall wrap *gomock.Call
|
||||
type BuildBuildOptionsCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *BuildBuildOptionsCall) Return(arg0 pb.BuildOptions) *BuildBuildOptionsCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *BuildBuildOptionsCall) Do(f func() pb.BuildOptions) *BuildBuildOptionsCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *BuildBuildOptionsCall) DoAndReturn(f func() pb.BuildOptions) *BuildBuildOptionsCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// Inline mocks base method.
|
||||
func (m *MockBuild) Inline() string {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Inline")
|
||||
ret0, _ := ret[0].(string)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Inline indicates an expected call of Inline.
|
||||
func (mr *MockBuildMockRecorder) Inline() *BuildInlineCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Inline", reflect.TypeOf((*MockBuild)(nil).Inline))
|
||||
return &BuildInlineCall{Call: call}
|
||||
}
|
||||
|
||||
// BuildInlineCall wrap *gomock.Call
|
||||
type BuildInlineCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *BuildInlineCall) Return(arg0 string) *BuildInlineCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *BuildInlineCall) Do(f func() string) *BuildInlineCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *BuildInlineCall) DoAndReturn(f func() string) *BuildInlineCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// Secrets mocks base method.
|
||||
func (m *MockBuild) Secrets() session.Attachable {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Secrets")
|
||||
ret0, _ := ret[0].(session.Attachable)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Secrets indicates an expected call of Secrets.
|
||||
func (mr *MockBuildMockRecorder) Secrets() *BuildSecretsCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Secrets", reflect.TypeOf((*MockBuild)(nil).Secrets))
|
||||
return &BuildSecretsCall{Call: call}
|
||||
}
|
||||
|
||||
// BuildSecretsCall wrap *gomock.Call
|
||||
type BuildSecretsCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *BuildSecretsCall) Return(arg0 session.Attachable) *BuildSecretsCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *BuildSecretsCall) Do(f func() session.Attachable) *BuildSecretsCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *BuildSecretsCall) DoAndReturn(f func() session.Attachable) *BuildSecretsCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// ShouldExec mocks base method.
|
||||
func (m *MockBuild) ShouldExec() bool {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "ShouldExec")
|
||||
ret0, _ := ret[0].(bool)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// ShouldExec indicates an expected call of ShouldExec.
|
||||
func (mr *MockBuildMockRecorder) ShouldExec() *BuildShouldExecCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ShouldExec", reflect.TypeOf((*MockBuild)(nil).ShouldExec))
|
||||
return &BuildShouldExecCall{Call: call}
|
||||
}
|
||||
|
||||
// BuildShouldExecCall wrap *gomock.Call
|
||||
type BuildShouldExecCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *BuildShouldExecCall) Return(arg0 bool) *BuildShouldExecCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *BuildShouldExecCall) Do(f func() bool) *BuildShouldExecCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *BuildShouldExecCall) DoAndReturn(f func() bool) *BuildShouldExecCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
386
provider/internal/mockprovidercontext_test.go
generated
Normal file
386
provider/internal/mockprovidercontext_test.go
generated
Normal file
@@ -0,0 +1,386 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: providercontext.go
|
||||
//
|
||||
// Generated by this command:
|
||||
//
|
||||
// mockgen -typed -package internal -source providercontext.go -destination mockprovidercontext_test.go --self_package github.com/pulumi/pulumi-docker/provider/v4/internal
|
||||
//
|
||||
// Package internal is a generated GoMock package.
|
||||
package internal
|
||||
|
||||
import (
|
||||
reflect "reflect"
|
||||
time "time"
|
||||
|
||||
provider "github.com/pulumi/pulumi-go-provider"
|
||||
diag "github.com/pulumi/pulumi/sdk/v3/go/common/diag"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
// MockProviderContext is a mock of ProviderContext interface.
|
||||
type MockProviderContext struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockProviderContextMockRecorder
|
||||
}
|
||||
|
||||
// MockProviderContextMockRecorder is the mock recorder for MockProviderContext.
|
||||
type MockProviderContextMockRecorder struct {
|
||||
mock *MockProviderContext
|
||||
}
|
||||
|
||||
// NewMockProviderContext creates a new mock instance.
|
||||
func NewMockProviderContext(ctrl *gomock.Controller) *MockProviderContext {
|
||||
mock := &MockProviderContext{ctrl: ctrl}
|
||||
mock.recorder = &MockProviderContextMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockProviderContext) EXPECT() *MockProviderContextMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Deadline mocks base method.
|
||||
func (m *MockProviderContext) Deadline() (time.Time, bool) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Deadline")
|
||||
ret0, _ := ret[0].(time.Time)
|
||||
ret1, _ := ret[1].(bool)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// Deadline indicates an expected call of Deadline.
|
||||
func (mr *MockProviderContextMockRecorder) Deadline() *ProviderContextDeadlineCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Deadline", reflect.TypeOf((*MockProviderContext)(nil).Deadline))
|
||||
return &ProviderContextDeadlineCall{Call: call}
|
||||
}
|
||||
|
||||
// ProviderContextDeadlineCall wrap *gomock.Call
|
||||
type ProviderContextDeadlineCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *ProviderContextDeadlineCall) Return(deadline time.Time, ok bool) *ProviderContextDeadlineCall {
|
||||
c.Call = c.Call.Return(deadline, ok)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *ProviderContextDeadlineCall) Do(f func() (time.Time, bool)) *ProviderContextDeadlineCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *ProviderContextDeadlineCall) DoAndReturn(f func() (time.Time, bool)) *ProviderContextDeadlineCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// Done mocks base method.
|
||||
func (m *MockProviderContext) Done() <-chan struct{} {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Done")
|
||||
ret0, _ := ret[0].(<-chan struct{})
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Done indicates an expected call of Done.
|
||||
func (mr *MockProviderContextMockRecorder) Done() *ProviderContextDoneCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Done", reflect.TypeOf((*MockProviderContext)(nil).Done))
|
||||
return &ProviderContextDoneCall{Call: call}
|
||||
}
|
||||
|
||||
// ProviderContextDoneCall wrap *gomock.Call
|
||||
type ProviderContextDoneCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *ProviderContextDoneCall) Return(arg0 <-chan struct{}) *ProviderContextDoneCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *ProviderContextDoneCall) Do(f func() <-chan struct{}) *ProviderContextDoneCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *ProviderContextDoneCall) DoAndReturn(f func() <-chan struct{}) *ProviderContextDoneCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// Err mocks base method.
|
||||
func (m *MockProviderContext) Err() error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Err")
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Err indicates an expected call of Err.
|
||||
func (mr *MockProviderContextMockRecorder) Err() *ProviderContextErrCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Err", reflect.TypeOf((*MockProviderContext)(nil).Err))
|
||||
return &ProviderContextErrCall{Call: call}
|
||||
}
|
||||
|
||||
// ProviderContextErrCall wrap *gomock.Call
|
||||
type ProviderContextErrCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *ProviderContextErrCall) Return(arg0 error) *ProviderContextErrCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *ProviderContextErrCall) Do(f func() error) *ProviderContextErrCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *ProviderContextErrCall) DoAndReturn(f func() error) *ProviderContextErrCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// Log mocks base method.
|
||||
func (m *MockProviderContext) Log(severity diag.Severity, msg string) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "Log", severity, msg)
|
||||
}
|
||||
|
||||
// Log indicates an expected call of Log.
|
||||
func (mr *MockProviderContextMockRecorder) Log(severity, msg any) *ProviderContextLogCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Log", reflect.TypeOf((*MockProviderContext)(nil).Log), severity, msg)
|
||||
return &ProviderContextLogCall{Call: call}
|
||||
}
|
||||
|
||||
// ProviderContextLogCall wrap *gomock.Call
|
||||
type ProviderContextLogCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *ProviderContextLogCall) Return() *ProviderContextLogCall {
|
||||
c.Call = c.Call.Return()
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *ProviderContextLogCall) Do(f func(diag.Severity, string)) *ProviderContextLogCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *ProviderContextLogCall) DoAndReturn(f func(diag.Severity, string)) *ProviderContextLogCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// LogStatus mocks base method.
|
||||
func (m *MockProviderContext) LogStatus(severity diag.Severity, msg string) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "LogStatus", severity, msg)
|
||||
}
|
||||
|
||||
// LogStatus indicates an expected call of LogStatus.
|
||||
func (mr *MockProviderContextMockRecorder) LogStatus(severity, msg any) *ProviderContextLogStatusCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LogStatus", reflect.TypeOf((*MockProviderContext)(nil).LogStatus), severity, msg)
|
||||
return &ProviderContextLogStatusCall{Call: call}
|
||||
}
|
||||
|
||||
// ProviderContextLogStatusCall wrap *gomock.Call
|
||||
type ProviderContextLogStatusCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *ProviderContextLogStatusCall) Return() *ProviderContextLogStatusCall {
|
||||
c.Call = c.Call.Return()
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *ProviderContextLogStatusCall) Do(f func(diag.Severity, string)) *ProviderContextLogStatusCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *ProviderContextLogStatusCall) DoAndReturn(f func(diag.Severity, string)) *ProviderContextLogStatusCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// LogStatusf mocks base method.
|
||||
func (m *MockProviderContext) LogStatusf(severity diag.Severity, msg string, args ...any) {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []any{severity, msg}
|
||||
for _, a := range args {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
m.ctrl.Call(m, "LogStatusf", varargs...)
|
||||
}
|
||||
|
||||
// LogStatusf indicates an expected call of LogStatusf.
|
||||
func (mr *MockProviderContextMockRecorder) LogStatusf(severity, msg any, args ...any) *ProviderContextLogStatusfCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]any{severity, msg}, args...)
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LogStatusf", reflect.TypeOf((*MockProviderContext)(nil).LogStatusf), varargs...)
|
||||
return &ProviderContextLogStatusfCall{Call: call}
|
||||
}
|
||||
|
||||
// ProviderContextLogStatusfCall wrap *gomock.Call
|
||||
type ProviderContextLogStatusfCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *ProviderContextLogStatusfCall) Return() *ProviderContextLogStatusfCall {
|
||||
c.Call = c.Call.Return()
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *ProviderContextLogStatusfCall) Do(f func(diag.Severity, string, ...any)) *ProviderContextLogStatusfCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *ProviderContextLogStatusfCall) DoAndReturn(f func(diag.Severity, string, ...any)) *ProviderContextLogStatusfCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// Logf mocks base method.
|
||||
func (m *MockProviderContext) Logf(severity diag.Severity, msg string, args ...any) {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []any{severity, msg}
|
||||
for _, a := range args {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
m.ctrl.Call(m, "Logf", varargs...)
|
||||
}
|
||||
|
||||
// Logf indicates an expected call of Logf.
|
||||
func (mr *MockProviderContextMockRecorder) Logf(severity, msg any, args ...any) *ProviderContextLogfCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]any{severity, msg}, args...)
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Logf", reflect.TypeOf((*MockProviderContext)(nil).Logf), varargs...)
|
||||
return &ProviderContextLogfCall{Call: call}
|
||||
}
|
||||
|
||||
// ProviderContextLogfCall wrap *gomock.Call
|
||||
type ProviderContextLogfCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *ProviderContextLogfCall) Return() *ProviderContextLogfCall {
|
||||
c.Call = c.Call.Return()
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *ProviderContextLogfCall) Do(f func(diag.Severity, string, ...any)) *ProviderContextLogfCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *ProviderContextLogfCall) DoAndReturn(f func(diag.Severity, string, ...any)) *ProviderContextLogfCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// RuntimeInformation mocks base method.
|
||||
func (m *MockProviderContext) RuntimeInformation() provider.RunInfo {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "RuntimeInformation")
|
||||
ret0, _ := ret[0].(provider.RunInfo)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// RuntimeInformation indicates an expected call of RuntimeInformation.
|
||||
func (mr *MockProviderContextMockRecorder) RuntimeInformation() *ProviderContextRuntimeInformationCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RuntimeInformation", reflect.TypeOf((*MockProviderContext)(nil).RuntimeInformation))
|
||||
return &ProviderContextRuntimeInformationCall{Call: call}
|
||||
}
|
||||
|
||||
// ProviderContextRuntimeInformationCall wrap *gomock.Call
|
||||
type ProviderContextRuntimeInformationCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *ProviderContextRuntimeInformationCall) Return(arg0 provider.RunInfo) *ProviderContextRuntimeInformationCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *ProviderContextRuntimeInformationCall) Do(f func() provider.RunInfo) *ProviderContextRuntimeInformationCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *ProviderContextRuntimeInformationCall) DoAndReturn(f func() provider.RunInfo) *ProviderContextRuntimeInformationCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// Value mocks base method.
|
||||
func (m *MockProviderContext) Value(key any) any {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Value", key)
|
||||
ret0, _ := ret[0].(any)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Value indicates an expected call of Value.
|
||||
func (mr *MockProviderContextMockRecorder) Value(key any) *ProviderContextValueCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Value", reflect.TypeOf((*MockProviderContext)(nil).Value), key)
|
||||
return &ProviderContextValueCall{Call: call}
|
||||
}
|
||||
|
||||
// ProviderContextValueCall wrap *gomock.Call
|
||||
type ProviderContextValueCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *ProviderContextValueCall) Return(arg0 any) *ProviderContextValueCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *ProviderContextValueCall) Do(f func(any) any) *ProviderContextValueCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *ProviderContextValueCall) DoAndReturn(f func(any) any) *ProviderContextValueCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
53
provider/internal/network.go
Normal file
53
provider/internal/network.go
Normal file
@@ -0,0 +1,53 @@
|
||||
// Copyright 2024, 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 internal
|
||||
|
||||
import "github.com/pulumi/pulumi-go-provider/infer"
|
||||
|
||||
var _ = (infer.Enum[NetworkMode])((*NetworkMode)(nil))
|
||||
|
||||
// NetworkMode is the --network parameter for a build.
|
||||
type NetworkMode string
|
||||
|
||||
const (
|
||||
Default NetworkMode = "default" // Default network mode.
|
||||
Host NetworkMode = "host" // Host network mode.
|
||||
None NetworkMode = "none" // None or no network mode.
|
||||
)
|
||||
|
||||
// Values returns all valid NetworkMode values for SDK generation.
|
||||
func (NetworkMode) Values() []infer.EnumValue[NetworkMode] {
|
||||
return []infer.EnumValue[NetworkMode]{
|
||||
{
|
||||
Value: Default,
|
||||
Description: "The default sandbox network mode.",
|
||||
},
|
||||
{
|
||||
Value: Host,
|
||||
Description: "Host network mode.",
|
||||
},
|
||||
{
|
||||
Value: None,
|
||||
Description: "Disable network access.",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (n *NetworkMode) String() string {
|
||||
if n == nil {
|
||||
return string(Default)
|
||||
}
|
||||
return string(*n)
|
||||
}
|
||||
79
provider/internal/platform.go
Normal file
79
provider/internal/platform.go
Normal file
@@ -0,0 +1,79 @@
|
||||
// Copyright 2024, 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 internal
|
||||
|
||||
import (
|
||||
"github.com/docker/buildx/util/platformutil"
|
||||
|
||||
"github.com/pulumi/pulumi-go-provider/infer"
|
||||
)
|
||||
|
||||
// These enum values are derived from
|
||||
// https://github.com/docker/cli/blob/d1b88930/cli/command/manifest/util.go#L22-L51
|
||||
|
||||
var _ = (infer.Enum[Platform])((*Platform)(nil))
|
||||
|
||||
// Platform is an enum capturing all available OS/architecture targets.
|
||||
type Platform string
|
||||
|
||||
// Values returns all valid Platform values for SDK generation.
|
||||
func (Platform) Values() []infer.EnumValue[Platform] {
|
||||
return []infer.EnumValue[Platform]{
|
||||
{Value: "darwin/386"},
|
||||
{Value: "darwin/amd64"},
|
||||
{Value: "darwin/arm"},
|
||||
{Value: "darwin/arm64"},
|
||||
{Value: "dragonfly/amd64"},
|
||||
{Value: "freebsd/386"},
|
||||
{Value: "freebsd/amd64"},
|
||||
{Value: "freebsd/arm"},
|
||||
{Value: "linux/386"},
|
||||
{Value: "linux/amd64"},
|
||||
{Value: "linux/arm"},
|
||||
{Value: "linux/arm64"},
|
||||
{Value: "linux/mips64"},
|
||||
{Value: "linux/mips64le"},
|
||||
{Value: "linux/ppc64le"},
|
||||
{Value: "linux/riscv64"},
|
||||
{Value: "linux/s390x"},
|
||||
{Value: "netbsd/386"},
|
||||
{Value: "netbsd/amd64"},
|
||||
{Value: "netbsd/arm"},
|
||||
{Value: "openbsd/386"},
|
||||
{Value: "openbsd/amd64"},
|
||||
{Value: "openbsd/arm"},
|
||||
{Value: "plan9/386"},
|
||||
{Value: "plan9/amd64"},
|
||||
{Value: "solaris/amd64"},
|
||||
{Value: "windows/386"},
|
||||
{Value: "windows/amd64"},
|
||||
}
|
||||
}
|
||||
|
||||
func (p Platform) String() string {
|
||||
return string(p)
|
||||
}
|
||||
|
||||
func (p Platform) validate(preview bool) (string, error) {
|
||||
if preview && p == "" {
|
||||
// Unknown platform during preview -- nothing to do.
|
||||
return "", nil
|
||||
}
|
||||
_, err := platformutil.Parse([]string{string(p)})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(p), nil
|
||||
}
|
||||
117
provider/internal/preview.go
Normal file
117
provider/internal/preview.go
Normal file
@@ -0,0 +1,117 @@
|
||||
// Copyright 2024, 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 internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// keeper decides whether an element should be included for a preview
|
||||
// operation, optionally returning a mutated copy of that element.
|
||||
type keeper[T any] interface {
|
||||
keep(T) bool
|
||||
}
|
||||
|
||||
// filter applies a keeper to each element, returning a new slice.
|
||||
func filter[T any](k keeper[T], elems ...T) []T {
|
||||
if elems == nil {
|
||||
return nil
|
||||
}
|
||||
result := make([]T, 0, len(elems))
|
||||
for _, e := range elems {
|
||||
if !k.keep(e) {
|
||||
continue
|
||||
}
|
||||
result = append(result, e)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// stringKeeper preserves any non-empty string values for preview.
|
||||
type stringKeeper struct{ preview bool }
|
||||
|
||||
func (k stringKeeper) keep(s string) bool {
|
||||
if !k.preview {
|
||||
return true
|
||||
}
|
||||
return s != ""
|
||||
}
|
||||
|
||||
//nolint:structcheck // False positive due to generics.
|
||||
type stringerKeeper[T fmt.Stringer] struct{ preview bool }
|
||||
|
||||
//nolint:unused // False positive due to generics.
|
||||
func (k stringerKeeper[T]) keep(t T) bool {
|
||||
if !k.preview {
|
||||
return true
|
||||
}
|
||||
return stringKeeper(k).keep(t.String())
|
||||
}
|
||||
|
||||
// registryKeeper preserves any registries with known values for address and
|
||||
// password. This is imprecise and doesn't permit alternative auth strategies
|
||||
// like registry tokens, email, etc.
|
||||
type registryKeeper struct{ preview bool }
|
||||
|
||||
//nolint:unused // False positive due to generics.
|
||||
func (k registryKeeper) keep(r Registry) bool {
|
||||
if !k.preview {
|
||||
return true
|
||||
}
|
||||
return r.Password != "" && r.Address != ""
|
||||
}
|
||||
|
||||
// mapKeeper preserves map elements with known keys and values.
|
||||
type mapKeeper struct{ preview bool }
|
||||
|
||||
func (k mapKeeper) keep(m map[string]string) map[string]string {
|
||||
if !k.preview || len(m) == 0 {
|
||||
return m
|
||||
}
|
||||
kk := stringKeeper(k)
|
||||
filtered := make(map[string]string)
|
||||
for key, val := range m {
|
||||
if !kk.keep(key) {
|
||||
continue
|
||||
}
|
||||
if !kk.keep(val) {
|
||||
continue
|
||||
}
|
||||
filtered[key] = val
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
type contextKeeper struct{ preview bool }
|
||||
|
||||
func (k contextKeeper) keep(bc *BuildContext) *BuildContext {
|
||||
if !k.preview || bc == nil || len(bc.Named) == 0 {
|
||||
return bc
|
||||
}
|
||||
|
||||
named := NamedContexts{}
|
||||
sk := stringKeeper(k)
|
||||
for k, v := range bc.Named {
|
||||
if !sk.keep(k) || !sk.keep(v.Location) {
|
||||
continue
|
||||
}
|
||||
named[k] = v
|
||||
}
|
||||
|
||||
return &BuildContext{
|
||||
Context: Context{bc.Location},
|
||||
Named: named,
|
||||
}
|
||||
}
|
||||
135
provider/internal/provider.go
Normal file
135
provider/internal/provider.go
Normal file
@@ -0,0 +1,135 @@
|
||||
// Copyright 2024, 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 internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
provider "github.com/pulumi/pulumi-go-provider"
|
||||
"github.com/pulumi/pulumi-go-provider/infer"
|
||||
pschema "github.com/pulumi/pulumi-go-provider/middleware/schema"
|
||||
"github.com/pulumi/pulumi-java/pkg/codegen/java"
|
||||
csgen "github.com/pulumi/pulumi/pkg/v3/codegen/dotnet"
|
||||
gogen "github.com/pulumi/pulumi/pkg/v3/codegen/go"
|
||||
tsgen "github.com/pulumi/pulumi/pkg/v3/codegen/nodejs"
|
||||
pygen "github.com/pulumi/pulumi/pkg/v3/codegen/python"
|
||||
"github.com/pulumi/pulumi/pkg/v3/codegen/schema"
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/common/tokens"
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
|
||||
)
|
||||
|
||||
var (
|
||||
_ infer.CustomConfigure = (*Config)(nil)
|
||||
_ infer.Annotated = (*Config)(nil)
|
||||
_ infer.Annotated = (*Registry)(nil)
|
||||
)
|
||||
|
||||
// Config configures the buildx provider.
|
||||
type Config struct {
|
||||
Host string `pulumi:"host,optional"`
|
||||
Registries []Registry `pulumi:"registries,optional"`
|
||||
|
||||
host *host
|
||||
}
|
||||
|
||||
// _mockClientKey is used by tests to inject a mock Docker client.
|
||||
var _mockClientKey struct{}
|
||||
|
||||
// Annotate provides user-facing descriptions and defaults for Config's fields.
|
||||
func (c *Config) Annotate(a infer.Annotator) {
|
||||
a.Describe(&c.Host, "The build daemon's address.")
|
||||
a.SetDefault(&c.Host, "", "DOCKER_HOST")
|
||||
}
|
||||
|
||||
// Configure validates and processes user-provided configuration values.
|
||||
func (c *Config) Configure(_ provider.Context) error {
|
||||
h, err := newHost(c)
|
||||
if err != nil {
|
||||
return fmt.Errorf("getting host: %w", err)
|
||||
}
|
||||
c.host = h
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewBuildxProvider returns a new buildx provider.
|
||||
func NewBuildxProvider() provider.Provider {
|
||||
return infer.Provider(
|
||||
infer.Options{
|
||||
Metadata: pschema.Metadata{
|
||||
DisplayName: "docker-build",
|
||||
Keywords: []string{"docker", "buildkit", "buildx", "kind/native"},
|
||||
Description: "A Pulumi provider for building modern Docker images with buildx and BuildKit.",
|
||||
Homepage: "https://pulumi.com",
|
||||
Publisher: "pulumi",
|
||||
License: "Apache-2.0",
|
||||
Repository: "https://github.com/pulumi/pulumi-docker-build",
|
||||
LanguageMap: map[string]any{
|
||||
"go": gogen.GoPackageInfo{
|
||||
// GenerateResourceContainerTypes: true,
|
||||
Generics: gogen.GenericsSettingSideBySide,
|
||||
PackageImportAliases: map[string]string{
|
||||
"github.com/pulumi/pulumi-docker-build/sdk/go/dockerbuild": "dockerbuild",
|
||||
},
|
||||
ImportBasePath: "github.com/pulumi/pulumi-docker-build/sdk/go/dockerbuild",
|
||||
},
|
||||
"csharp": csgen.CSharpPackageInfo{
|
||||
PackageReferences: map[string]string{
|
||||
"Pulumi": "3.*",
|
||||
},
|
||||
},
|
||||
"java": java.PackageInfo{
|
||||
BuildFiles: "gradle",
|
||||
GradleNexusPublishPluginVersion: "1.1.0",
|
||||
Dependencies: map[string]string{
|
||||
"com.pulumi:pulumi": "0.9.9",
|
||||
"com.google.code.gson:gson": "2.8.9",
|
||||
"com.google.code.findbugs:jsr305": "3.0.2",
|
||||
},
|
||||
},
|
||||
"nodejs": tsgen.NodePackageInfo{
|
||||
Dependencies: map[string]string{
|
||||
"@pulumi/pulumi": "^3.0.0",
|
||||
},
|
||||
},
|
||||
"python": pygen.PackageInfo{
|
||||
PyProject: struct {
|
||||
Enabled bool `json:"enabled,omitempty"`
|
||||
}{Enabled: true},
|
||||
Requires: map[string]string{
|
||||
"pulumi": ">=3.0.0,<4.0.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Resources: []infer.InferredResource{
|
||||
infer.Resource[*Image](),
|
||||
infer.Resource[*Index](),
|
||||
},
|
||||
ModuleMap: map[tokens.ModuleName]tokens.ModuleName{
|
||||
"internal": "index",
|
||||
},
|
||||
Config: infer.Config[*Config](),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// Schema returns our package specification.
|
||||
func Schema(ctx context.Context, version string) schema.PackageSpec {
|
||||
p := NewBuildxProvider()
|
||||
spec, err := provider.GetSchema(ctx, "docker-build", version, p)
|
||||
contract.AssertNoErrorf(err, "missing schema")
|
||||
return spec
|
||||
}
|
||||
82
provider/internal/provider_test.go
Normal file
82
provider/internal/provider_test.go
Normal file
@@ -0,0 +1,82 @@
|
||||
// Copyright 2024, 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 internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/blang/semver"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
provider "github.com/pulumi/pulumi-go-provider"
|
||||
"github.com/pulumi/pulumi-go-provider/infer"
|
||||
"github.com/pulumi/pulumi-go-provider/integration"
|
||||
mwcontext "github.com/pulumi/pulumi-go-provider/middleware/context"
|
||||
)
|
||||
|
||||
func TestConfigure(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
s := newServer(nil)
|
||||
|
||||
err := s.Configure(
|
||||
provider.ConfigureRequest{},
|
||||
)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
// TestAnnotate sanity checks that our annotations don't panic.
|
||||
func TestAnnotate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, tt := range []infer.Annotated{
|
||||
&Config{},
|
||||
&Image{},
|
||||
&ImageArgs{},
|
||||
&ImageState{},
|
||||
&Index{},
|
||||
&IndexArgs{},
|
||||
&IndexState{},
|
||||
} {
|
||||
tt.Annotate(annotator{})
|
||||
}
|
||||
}
|
||||
|
||||
// TestSchema sanity checks that our schema doesn't panic.
|
||||
func TestSchema(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
Schema(context.Background(), "v4")
|
||||
}
|
||||
|
||||
type annotator struct{}
|
||||
|
||||
func (annotator) Describe(_ any, _ string) {}
|
||||
func (annotator) SetDefault(_, _ any, _ ...string) {}
|
||||
func (annotator) SetToken(_, _ string) {}
|
||||
|
||||
func newServer(client Client) integration.Server {
|
||||
p := NewBuildxProvider()
|
||||
|
||||
// Inject a mock client if provided.
|
||||
if client != nil {
|
||||
p = mwcontext.Wrap(p, func(ctx provider.Context) provider.Context {
|
||||
return provider.CtxWithValue(ctx, _mockClientKey, client)
|
||||
})
|
||||
}
|
||||
|
||||
return integration.NewServer("docker", semver.Version{Major: 4}, p)
|
||||
}
|
||||
27
provider/internal/providercontext.go
Normal file
27
provider/internal/providercontext.go
Normal file
@@ -0,0 +1,27 @@
|
||||
// Copyright 2024, 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.
|
||||
|
||||
//go:generate go run go.uber.org/mock/mockgen -typed -package internal -source providercontext.go -destination mockprovidercontext_test.go --self_package github.com/pulumi/pulumi-docker/provider/v4/internal
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
provider "github.com/pulumi/pulumi-go-provider"
|
||||
)
|
||||
|
||||
// ProviderContext is a workaround for
|
||||
// https://github.com/pulumi/pulumi-go-provider/issues/159
|
||||
type ProviderContext interface {
|
||||
provider.Context
|
||||
}
|
||||
79
provider/internal/ssh.go
Normal file
79
provider/internal/ssh.go
Normal file
@@ -0,0 +1,79 @@
|
||||
// Copyright 2024, 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 internal
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
controllerapi "github.com/docker/buildx/controller/pb"
|
||||
"github.com/docker/buildx/util/buildflags"
|
||||
|
||||
"github.com/pulumi/pulumi-go-provider/infer"
|
||||
)
|
||||
|
||||
// SSH is an SSH option.
|
||||
type SSH struct {
|
||||
ID string `pulumi:"id"`
|
||||
Paths []string `pulumi:"paths,optional"`
|
||||
}
|
||||
|
||||
// Annotate sets docstrings on SSH.
|
||||
func (s *SSH) Annotate(a infer.Annotator) {
|
||||
a.Describe(&s.ID, dedent(`
|
||||
Useful for distinguishing different servers that are part of the same
|
||||
build.
|
||||
|
||||
A value of "default" is appropriate if only dealing with a single host.
|
||||
`))
|
||||
a.Describe(&s.Paths, dedent(`
|
||||
SSH agent socket or private keys to expose to the build under the given
|
||||
identifier.
|
||||
|
||||
Defaults to "[$SSH_AUTH_SOCK]".
|
||||
|
||||
Note that your keys are **not** automatically added when using an
|
||||
agent. Run "ssh-add -l" locally to confirm which public keys are
|
||||
visible to the agent; these will be exposed to your build.
|
||||
`))
|
||||
}
|
||||
|
||||
// String returns a CLI-encoded value for the SSH option, or an empty string if
|
||||
// its ID is not known.
|
||||
func (s SSH) String() string {
|
||||
if s.ID == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
r := s.ID
|
||||
|
||||
if len(s.Paths) > 0 {
|
||||
r += "=" + strings.Join(s.Paths, ",")
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (s SSH) validate() (*controllerapi.SSH, error) {
|
||||
parsed, err := buildflags.ParseSSHSpecs([]string{s.String()})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(parsed) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
_, err = controllerapi.CreateSSH([]*controllerapi.SSH{{ID: s.ID, Paths: s.Paths}})
|
||||
return parsed[0], err
|
||||
}
|
||||
52
provider/internal/ssh_test.go
Normal file
52
provider/internal/ssh_test.go
Normal file
@@ -0,0 +1,52 @@
|
||||
// Copyright 2024, 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 internal
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestValidateSSH(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
name string
|
||||
ssh SSH
|
||||
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "invalid path",
|
||||
ssh: SSH{ID: "foo", Paths: []string{"/not/real"}},
|
||||
wantErr: "/not/real: no such file or directory",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := tt.ssh.validate()
|
||||
|
||||
if tt.wantErr == "" {
|
||||
assert.NoError(t, err)
|
||||
} else {
|
||||
assert.ErrorContains(t, err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
2
provider/internal/testdata/Dockerfile.invalid
vendored
Normal file
2
provider/internal/testdata/Dockerfile.invalid
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
FROM scratch
|
||||
RUNN
|
||||
3
provider/internal/testdata/dockerfile-location-irrelevant/app/foo.sh
vendored
Normal file
3
provider/internal/testdata/dockerfile-location-irrelevant/app/foo.sh
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
echo "Hello, World!"
|
||||
5
provider/internal/testdata/dockerfile-location-irrelevant/step1.Dockerfile
vendored
Normal file
5
provider/internal/testdata/dockerfile-location-irrelevant/step1.Dockerfile
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
FROM scratch
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY ./bar .
|
||||
5
provider/internal/testdata/dockerfile-location-irrelevant/step2.Dockerfile
vendored
Normal file
5
provider/internal/testdata/dockerfile-location-irrelevant/step2.Dockerfile
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
FROM scratch
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY ./bar .
|
||||
5
provider/internal/testdata/filemode-matters/step1/Dockerfile
vendored
Normal file
5
provider/internal/testdata/filemode-matters/step1/Dockerfile
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
FROM scratch
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY ./bar .
|
||||
3
provider/internal/testdata/filemode-matters/step1/foo.sh
vendored
Normal file
3
provider/internal/testdata/filemode-matters/step1/foo.sh
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
echo "Hello, World!"
|
||||
5
provider/internal/testdata/filemode-matters/step2-chmod-x/Dockerfile
vendored
Normal file
5
provider/internal/testdata/filemode-matters/step2-chmod-x/Dockerfile
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
FROM scratch
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY ./bar .
|
||||
3
provider/internal/testdata/filemode-matters/step2-chmod-x/foo.sh
vendored
Executable file
3
provider/internal/testdata/filemode-matters/step2-chmod-x/foo.sh
vendored
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
echo "Hello, World!"
|
||||
7
provider/internal/testdata/ignores-wildcard/basedir-modified-ignored-file/.dockerignore
vendored
Normal file
7
provider/internal/testdata/ignores-wildcard/basedir-modified-ignored-file/.dockerignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
# Ignore everything!
|
||||
*
|
||||
|
||||
# Except changes to source data in this set of directories:
|
||||
!projects/*/src
|
||||
|
||||
# This should therefore IGNORE projects/*/dist
|
||||
5
provider/internal/testdata/ignores-wildcard/basedir-modified-ignored-file/Dockerfile
vendored
Normal file
5
provider/internal/testdata/ignores-wildcard/basedir-modified-ignored-file/Dockerfile
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
FROM scratch
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY ./bar .
|
||||
@@ -0,0 +1 @@
|
||||
console.log("Pulumi 💜");
|
||||
7
provider/internal/testdata/ignores-wildcard/basedir-modified-included-file/.dockerignore
vendored
Normal file
7
provider/internal/testdata/ignores-wildcard/basedir-modified-included-file/.dockerignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
# Ignore everything!
|
||||
*
|
||||
|
||||
# Except changes to source data in this set of directories:
|
||||
!projects/*/src
|
||||
|
||||
# This should therefore IGNORE projects/*/dist
|
||||
5
provider/internal/testdata/ignores-wildcard/basedir-modified-included-file/Dockerfile
vendored
Normal file
5
provider/internal/testdata/ignores-wildcard/basedir-modified-included-file/Dockerfile
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
FROM scratch
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY ./bar .
|
||||
@@ -0,0 +1 @@
|
||||
console.log("💜 Pulumi (included)");
|
||||
7
provider/internal/testdata/ignores-wildcard/basedir/.dockerignore
vendored
Normal file
7
provider/internal/testdata/ignores-wildcard/basedir/.dockerignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
# Ignore everything!
|
||||
*
|
||||
|
||||
# Except changes to source data in this set of directories:
|
||||
!projects/*/src
|
||||
|
||||
# This should therefore IGNORE projects/*/dist
|
||||
5
provider/internal/testdata/ignores-wildcard/basedir/Dockerfile
vendored
Normal file
5
provider/internal/testdata/ignores-wildcard/basedir/Dockerfile
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
FROM scratch
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY ./bar .
|
||||
1
provider/internal/testdata/ignores-wildcard/basedir/projects/foo/src/app.js
vendored
Normal file
1
provider/internal/testdata/ignores-wildcard/basedir/projects/foo/src/app.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
console.log("Pulumi 💜");
|
||||
3
provider/internal/testdata/ignores/basedir-with-ignored-files/.dockerignore
vendored
Normal file
3
provider/internal/testdata/ignores/basedir-with-ignored-files/.dockerignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
.dockerignore
|
||||
ignored.txt
|
||||
bar/ignored.js
|
||||
5
provider/internal/testdata/ignores/basedir-with-ignored-files/Dockerfile
vendored
Normal file
5
provider/internal/testdata/ignores/basedir-with-ignored-files/Dockerfile
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
FROM scratch
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY ./bar .
|
||||
1
provider/internal/testdata/ignores/basedir-with-ignored-files/bar/app.js
vendored
Normal file
1
provider/internal/testdata/ignores/basedir-with-ignored-files/bar/app.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
console.log("Pulumi 💜");
|
||||
0
provider/internal/testdata/ignores/basedir-with-ignored-files/bar/ignored.js
vendored
Normal file
0
provider/internal/testdata/ignores/basedir-with-ignored-files/bar/ignored.js
vendored
Normal file
1
provider/internal/testdata/ignores/basedir-with-ignored-files/foo.txt
vendored
Normal file
1
provider/internal/testdata/ignores/basedir-with-ignored-files/foo.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
bar
|
||||
0
provider/internal/testdata/ignores/basedir-with-ignored-files/ignored.txt
vendored
Normal file
0
provider/internal/testdata/ignores/basedir-with-ignored-files/ignored.txt
vendored
Normal file
5
provider/internal/testdata/ignores/basedir/Dockerfile
vendored
Normal file
5
provider/internal/testdata/ignores/basedir/Dockerfile
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
FROM scratch
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY ./bar .
|
||||
1
provider/internal/testdata/ignores/basedir/bar/app.js
vendored
Normal file
1
provider/internal/testdata/ignores/basedir/bar/app.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
console.log("Pulumi 💜");
|
||||
1
provider/internal/testdata/ignores/basedir/foo.txt
vendored
Normal file
1
provider/internal/testdata/ignores/basedir/foo.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
bar
|
||||
1
provider/internal/testdata/noop/Dockerfile
vendored
Normal file
1
provider/internal/testdata/noop/Dockerfile
vendored
Normal file
@@ -0,0 +1 @@
|
||||
FROM scratch
|
||||
5
provider/internal/testdata/renaming-matters/step1/Dockerfile
vendored
Normal file
5
provider/internal/testdata/renaming-matters/step1/Dockerfile
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
FROM scratch
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY ./bar .
|
||||
1
provider/internal/testdata/renaming-matters/step1/foo.a.txt
vendored
Normal file
1
provider/internal/testdata/renaming-matters/step1/foo.a.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
Test
|
||||
5
provider/internal/testdata/renaming-matters/step2/Dockerfile
vendored
Normal file
5
provider/internal/testdata/renaming-matters/step2/Dockerfile
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
FROM scratch
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY ./bar .
|
||||
1
provider/internal/testdata/renaming-matters/step2/foo.b.txt
vendored
Normal file
1
provider/internal/testdata/renaming-matters/step2/foo.b.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
Test
|
||||
5
provider/internal/testdata/symlinks/Dockerfile
vendored
Normal file
5
provider/internal/testdata/symlinks/Dockerfile
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
FROM scratch
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY ./bar .
|
||||
1
provider/internal/testdata/symlinks/linkedFromDeep.txt
vendored
Normal file
1
provider/internal/testdata/symlinks/linkedFromDeep.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
Hello,
|
||||
1
provider/internal/testdata/symlinks/linkedToDeep.txt
vendored
Normal file
1
provider/internal/testdata/symlinks/linkedToDeep.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
World!
|
||||
1
provider/internal/testdata/symlinks/proxy-for-sub/dir/linkedFromTop.txt
vendored
Normal file
1
provider/internal/testdata/symlinks/proxy-for-sub/dir/linkedFromTop.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
World!
|
||||
1
provider/internal/testdata/symlinks/proxy-for-sub/dir/linkedToTop.txt
vendored
Normal file
1
provider/internal/testdata/symlinks/proxy-for-sub/dir/linkedToTop.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
Hello,
|
||||
1
provider/internal/testdata/symlinks/sub/dir/linkedFromTop.txt
vendored
Normal file
1
provider/internal/testdata/symlinks/sub/dir/linkedFromTop.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
World!
|
||||
1
provider/internal/testdata/symlinks/sub/dir/linkedToTop.txt
vendored
Normal file
1
provider/internal/testdata/symlinks/sub/dir/linkedToTop.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
Hello,
|
||||
6
provider/internal/testdata/unignores/basedir-with-unignored-files/.dockerignore
vendored
Normal file
6
provider/internal/testdata/unignores/basedir-with-unignored-files/.dockerignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
# Ignore everything
|
||||
*
|
||||
|
||||
#Unignore everything
|
||||
!Dockerfile
|
||||
!top-dir
|
||||
5
provider/internal/testdata/unignores/basedir-with-unignored-files/Dockerfile
vendored
Normal file
5
provider/internal/testdata/unignores/basedir-with-unignored-files/Dockerfile
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
FROM scratch
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY ./bar .
|
||||
1
provider/internal/testdata/unignores/basedir-with-unignored-files/top-dir/subdir/app.js
vendored
Normal file
1
provider/internal/testdata/unignores/basedir-with-unignored-files/top-dir/subdir/app.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
console.log("Pulumi 💜");
|
||||
0
provider/internal/testdata/unignores/basedir-with-unignored-files/top-dir/subdir/ignored.js
vendored
Normal file
0
provider/internal/testdata/unignores/basedir-with-unignored-files/top-dir/subdir/ignored.js
vendored
Normal file
5
provider/internal/testdata/unignores/basedir/Dockerfile
vendored
Normal file
5
provider/internal/testdata/unignores/basedir/Dockerfile
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
FROM scratch
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY ./bar .
|
||||
1
provider/internal/testdata/unignores/basedir/top-dir/subdir/app.js
vendored
Normal file
1
provider/internal/testdata/unignores/basedir/top-dir/subdir/app.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
console.log("Pulumi 💜");
|
||||
0
provider/internal/testdata/unignores/basedir/top-dir/subdir/ignored.js
vendored
Normal file
0
provider/internal/testdata/unignores/basedir/top-dir/subdir/ignored.js
vendored
Normal file
Reference in New Issue
Block a user