mirror of https://github.com/docker/compose.git
				
				
				
			add warning message when a remote configuration include an another remote config
Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com>
This commit is contained in:
		
							parent
							
								
									66a47169d5
								
							
						
					
					
						commit
						41e6094041
					
				|  | @ -29,6 +29,7 @@ import ( | ||||||
| 	"github.com/compose-spec/compose-go/v2/template" | 	"github.com/compose-spec/compose-go/v2/template" | ||||||
| 	"github.com/compose-spec/compose-go/v2/types" | 	"github.com/compose-spec/compose-go/v2/types" | ||||||
| 	"github.com/docker/cli/cli/command" | 	"github.com/docker/cli/cli/command" | ||||||
|  | 	"github.com/docker/compose/v2/internal/tracing" | ||||||
| 	ui "github.com/docker/compose/v2/pkg/progress" | 	ui "github.com/docker/compose/v2/pkg/progress" | ||||||
| 	"github.com/docker/compose/v2/pkg/prompt" | 	"github.com/docker/compose/v2/pkg/prompt" | ||||||
| 	"github.com/docker/compose/v2/pkg/utils" | 	"github.com/docker/compose/v2/pkg/utils" | ||||||
|  | @ -103,6 +104,11 @@ func checksForRemoteStack(ctx context.Context, dockerCli command.Cli, project *t | ||||||
| 	if !isRemoteConfig(dockerCli, options) { | 	if !isRemoteConfig(dockerCli, options) { | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
|  | 	if metrics, ok := ctx.Value(tracing.MetricsKey{}).(tracing.Metrics); ok && metrics.CountIncludesRemote > 0 { | ||||||
|  | 		if err := confirmRemoteIncludes(dockerCli, options, assumeYes); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
| 	displayLocationRemoteStack(dockerCli, project, options) | 	displayLocationRemoteStack(dockerCli, project, options) | ||||||
| 	return promptForInterpolatedVariables(ctx, dockerCli, options.ProjectOptions, assumeYes, cmdEnvs) | 	return promptForInterpolatedVariables(ctx, dockerCli, options.ProjectOptions, assumeYes, cmdEnvs) | ||||||
| } | } | ||||||
|  | @ -245,3 +251,41 @@ func displayLocationRemoteStack(dockerCli command.Cli, project *types.Project, o | ||||||
| 		_, _ = fmt.Fprintf(dockerCli.Out(), "Your compose stack %q is stored in %q\n", mainComposeFile, project.WorkingDir) | 		_, _ = fmt.Fprintf(dockerCli.Out(), "Your compose stack %q is stored in %q\n", mainComposeFile, project.WorkingDir) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func confirmRemoteIncludes(dockerCli command.Cli, options buildOptions, assumeYes bool) error { | ||||||
|  | 	if assumeYes { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var remoteIncludes []string | ||||||
|  | 	remoteLoaders := options.ProjectOptions.remoteLoaders(dockerCli) | ||||||
|  | 	for _, cf := range options.ProjectOptions.ConfigPaths { | ||||||
|  | 		for _, loader := range remoteLoaders { | ||||||
|  | 			if loader.Accept(cf) { | ||||||
|  | 				remoteIncludes = append(remoteIncludes, cf) | ||||||
|  | 				break | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if len(remoteIncludes) == 0 { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	_, _ = fmt.Fprintln(dockerCli.Out(), "\nWarning: This Compose project includes files from remote sources:") | ||||||
|  | 	for _, include := range remoteIncludes { | ||||||
|  | 		_, _ = fmt.Fprintf(dockerCli.Out(), "  - %s\n", include) | ||||||
|  | 	} | ||||||
|  | 	_, _ = fmt.Fprintln(dockerCli.Out(), "\nRemote includes could potentially be malicious. Make sure you trust the source.") | ||||||
|  | 
 | ||||||
|  | 	msg := "Do you want to continue? [y/N]: " | ||||||
|  | 	confirmed, err := prompt.NewPrompt(dockerCli.In(), dockerCli.Out()).Confirm(msg, false) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if !confirmed { | ||||||
|  | 		return fmt.Errorf("operation cancelled by user") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -20,6 +20,7 @@ import ( | ||||||
| 	"bytes" | 	"bytes" | ||||||
| 	"context" | 	"context" | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"io" | ||||||
| 	"os" | 	"os" | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
| 	"strings" | 	"strings" | ||||||
|  | @ -280,3 +281,114 @@ services: | ||||||
| 		normalizeSpaces(actualOutput), | 		normalizeSpaces(actualOutput), | ||||||
| 		"\nExpected:\n%s\nGot:\n%s", expected, actualOutput) | 		"\nExpected:\n%s\nGot:\n%s", expected, actualOutput) | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func TestConfirmRemoteIncludes(t *testing.T) { | ||||||
|  | 	ctrl := gomock.NewController(t) | ||||||
|  | 	defer ctrl.Finish() | ||||||
|  | 	cli := mocks.NewMockCli(ctrl) | ||||||
|  | 
 | ||||||
|  | 	tests := []struct { | ||||||
|  | 		name       string | ||||||
|  | 		opts       buildOptions | ||||||
|  | 		assumeYes  bool | ||||||
|  | 		userInput  string | ||||||
|  | 		wantErr    bool | ||||||
|  | 		errMessage string | ||||||
|  | 		wantPrompt bool | ||||||
|  | 		wantOutput string | ||||||
|  | 	}{ | ||||||
|  | 		{ | ||||||
|  | 			name: "no remote includes", | ||||||
|  | 			opts: buildOptions{ | ||||||
|  | 				ProjectOptions: &ProjectOptions{ | ||||||
|  | 					ConfigPaths: []string{ | ||||||
|  | 						"docker-compose.yaml", | ||||||
|  | 						"./local/path/compose.yaml", | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			assumeYes:  false, | ||||||
|  | 			wantErr:    false, | ||||||
|  | 			wantPrompt: false, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name: "assume yes with remote includes", | ||||||
|  | 			opts: buildOptions{ | ||||||
|  | 				ProjectOptions: &ProjectOptions{ | ||||||
|  | 					ConfigPaths: []string{ | ||||||
|  | 						"oci://registry.example.com/stack:latest", | ||||||
|  | 						"git://github.com/user/repo.git", | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			assumeYes:  true, | ||||||
|  | 			wantErr:    false, | ||||||
|  | 			wantPrompt: false, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name: "user confirms remote includes", | ||||||
|  | 			opts: buildOptions{ | ||||||
|  | 				ProjectOptions: &ProjectOptions{ | ||||||
|  | 					ConfigPaths: []string{ | ||||||
|  | 						"oci://registry.example.com/stack:latest", | ||||||
|  | 						"git://github.com/user/repo.git", | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			assumeYes:  false, | ||||||
|  | 			userInput:  "y\n", | ||||||
|  | 			wantErr:    false, | ||||||
|  | 			wantPrompt: true, | ||||||
|  | 			wantOutput: "\nWarning: This Compose project includes files from remote sources:\n" + | ||||||
|  | 				"  - oci://registry.example.com/stack:latest\n" + | ||||||
|  | 				"  - git://github.com/user/repo.git\n" + | ||||||
|  | 				"\nRemote includes could potentially be malicious. Make sure you trust the source.\n" + | ||||||
|  | 				"Do you want to continue? [y/N]: ", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name: "user rejects remote includes", | ||||||
|  | 			opts: buildOptions{ | ||||||
|  | 				ProjectOptions: &ProjectOptions{ | ||||||
|  | 					ConfigPaths: []string{ | ||||||
|  | 						"oci://registry.example.com/stack:latest", | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			assumeYes:  false, | ||||||
|  | 			userInput:  "n\n", | ||||||
|  | 			wantErr:    true, | ||||||
|  | 			errMessage: "operation cancelled by user", | ||||||
|  | 			wantPrompt: true, | ||||||
|  | 			wantOutput: "\nWarning: This Compose project includes files from remote sources:\n" + | ||||||
|  | 				"  - oci://registry.example.com/stack:latest\n" + | ||||||
|  | 				"\nRemote includes could potentially be malicious. Make sure you trust the source.\n" + | ||||||
|  | 				"Do you want to continue? [y/N]: ", | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	buf := new(bytes.Buffer) | ||||||
|  | 	for _, tt := range tests { | ||||||
|  | 		t.Run(tt.name, func(t *testing.T) { | ||||||
|  | 			cli.EXPECT().Out().Return(streams.NewOut(buf)).AnyTimes() | ||||||
|  | 
 | ||||||
|  | 			if tt.wantPrompt { | ||||||
|  | 				inbuf := io.NopCloser(bytes.NewBufferString(tt.userInput)) | ||||||
|  | 				cli.EXPECT().In().Return(streams.NewIn(inbuf)).AnyTimes() | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			err := confirmRemoteIncludes(cli, tt.opts, tt.assumeYes) | ||||||
|  | 
 | ||||||
|  | 			if tt.wantErr { | ||||||
|  | 				require.Error(t, err) | ||||||
|  | 				require.Equal(t, tt.errMessage, err.Error()) | ||||||
|  | 			} else { | ||||||
|  | 				require.NoError(t, err) | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			if tt.wantOutput != "" { | ||||||
|  | 				require.Equal(t, tt.wantOutput, buf.String()) | ||||||
|  | 			} | ||||||
|  | 			buf.Reset() | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -96,6 +96,6 @@ type Pipe struct { | ||||||
| func (u Pipe) Confirm(message string, defaultValue bool) (bool, error) { | func (u Pipe) Confirm(message string, defaultValue bool) (bool, error) { | ||||||
| 	_, _ = fmt.Fprint(u.stdout, message) | 	_, _ = fmt.Fprint(u.stdout, message) | ||||||
| 	var answer string | 	var answer string | ||||||
| 	_, _ = fmt.Scanln(&answer) | 	_, _ = fmt.Fscanln(u.stdin, &answer) | ||||||
| 	return utils.StringToBool(answer), nil | 	return utils.StringToBool(answer), nil | ||||||
| } | } | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue