Skip to content

Commit

Permalink
feat: add 'skaffold inspect jobManifestPath' and 'skaffold transform-…
Browse files Browse the repository at this point in the history
…schema jobManifestPath' commands
  • Loading branch information
aaron-prindle committed Mar 24, 2023
1 parent 2413b9c commit 3b16ae3
Show file tree
Hide file tree
Showing 7 changed files with 548 additions and 1 deletion.
2 changes: 1 addition & 1 deletion cmd/skaffold/app/cmd/inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func NewCmdInspect() *cobra.Command {
WithDescription("Helper commands for Cloud Code IDEs to interact with and modify skaffold configuration files.").
WithPersistentFlagAdder(cmdInspectFlags).
Hidden().
WithCommands(cmdModules(), cmdProfiles(), cmdBuildEnv(), cmdTests(), cmdNamespaces())
WithCommands(cmdModules(), cmdProfiles(), cmdBuildEnv(), cmdTests(), cmdNamespaces(), cmdJobManifestPaths())
}

func cmdInspectFlags(f *pflag.FlagSet) {
Expand Down
85 changes: 85 additions & 0 deletions cmd/skaffold/app/cmd/inspect_job_manifest_paths.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
Copyright 2021 The Skaffold Authors
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 cmd

import (
"context"
"errors"
"io"

"github.com/spf13/cobra"
"github.com/spf13/pflag"

"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/inspect"
jobManifestPaths "github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/inspect/jobManifestPaths"
olog "github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/output/log"
)

func cmdJobManifestPaths() *cobra.Command {
return NewCmd("jobManifestPaths").
WithDescription("View skaffold jobManifestPath information defined in the specified skaffold configuration").
WithCommands(cmdJobManifestPathsList(), cmdJobManifestPathsModify())
}

func cmdJobManifestPathsList() *cobra.Command {
return NewCmd("list").
WithExample("Get list of jobManifestPaths", "inspect jobManifestPaths list --format json").
WithExample("Get list of jobManifestPaths targeting a specific configuration", "inspect jobManifestPaths list --profile local --format json").
WithDescription("Print the list of jobManifestPaths that would be run for a given configuration (default skaffold configuration, specific module, specific profile, etc).").
WithFlagAdder(cmdJobManifestPathsListFlags).
NoArgs(listJobManifestPaths)
}

func cmdJobManifestPathsModify() *cobra.Command {
return NewCmd("modify").
WithExample("Modify the skaffold verify jobManifestPaths", "inspect jobManifestPaths list --format json").
WithExample("Modify the jobManifestPaths targeting a specific configuration", "inspect jobManifestPaths modify --profile local --format json").
WithDescription("Print the list of jobManifestPaths that would be run for a given configuration (default skaffold configuration, specific module, specific profile, etc).").
WithCommonFlags().
WithFlags([]*Flag{
// TODO(aaron-prindle) vvv 2 commands use this, should add to common flags w/ those 2 commands added
{Value: &outputFile, Name: "output", DefValue: "", Usage: "File to write `inspect jobManifestPath modify` result"},
}).
WithArgs(func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
olog.Entry(context.TODO()).Errorf("`jobManifestPaths modify` requires exactly one manifest file path argument")
return errors.New("`jobManifestPaths modify` requires exactly one manifest file path argument")
}
return nil
}, modifyJobManifestPaths)
}

func listJobManifestPaths(ctx context.Context, out io.Writer) error {
return jobManifestPaths.PrintJobManifestPathsList(ctx, out, inspect.Options{
Filename: inspectFlags.filename,
RepoCacheDir: inspectFlags.repoCacheDir,
OutFormat: inspectFlags.outFormat,
Modules: inspectFlags.modules,
Profiles: inspectFlags.profiles,
PropagateProfiles: inspectFlags.propagateProfiles,
})
}

func cmdJobManifestPathsListFlags(f *pflag.FlagSet) {
f.StringSliceVarP(&inspectFlags.profiles, "profile", "p", nil, `Profile names to activate`)
f.BoolVar(&inspectFlags.propagateProfiles, "propagate-profiles", true, `Setting '--propagate-profiles=false' disables propagating profiles set by the '--profile' flag across config dependencies. This mean that only profiles defined directly in the target 'skaffold.yaml' file are activated.`)
f.StringSliceVarP(&inspectFlags.modules, "module", "m", nil, "Names of modules to filter target action by.")
}

func modifyJobManifestPaths(ctx context.Context, out io.Writer, args []string) error {
return jobManifestPaths.Modify(ctx, out, opts, args[0], outputFile)
}
2 changes: 2 additions & 0 deletions cmd/skaffold/app/cmd/inspect_namespaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (

"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/inspect"
namespaces "github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/inspect/namespaces"
olog "github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/output/log"
)

func cmdNamespaces() *cobra.Command {
Expand All @@ -42,6 +43,7 @@ func cmdNamespacesList() *cobra.Command {
WithFlagAdder(cmdNamespacesListFlags).
WithArgs(func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
olog.Entry(context.TODO()).Errorf("`inspect namespaces list` requires exactly one manifest file path argument")
return errors.New("`inspect namespaces list` requires exactly one manifest file path argument")
}
return nil
Expand Down
59 changes: 59 additions & 0 deletions pkg/skaffold/inspect/jobManifestPaths/list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
Copyright 2021 The Skaffold Authors
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 inspect

import (
"context"
"io"

"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/config"
"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/inspect"
)

type jobManifestPathList struct {
VerifyJobManifestPaths map[string]string `json:"verifyJobManifestPaths"`
CustomActionJobManifestPaths map[string]string `json:"customActionJobManifestPaths"`
}

func PrintJobManifestPathsList(ctx context.Context, out io.Writer, opts inspect.Options) error {
formatter := inspect.OutputFormatter(out, opts.OutFormat)
cfgs, err := inspect.GetConfigSet(ctx, config.SkaffoldOptions{
ConfigurationFile: opts.Filename,
ConfigurationFilter: opts.Modules,
RepoCacheDir: opts.RepoCacheDir,
Profiles: opts.Profiles,
PropagateProfiles: opts.PropagateProfiles,
})
if err != nil {
formatter.WriteErr(err)
return err
}

l := &jobManifestPathList{
VerifyJobManifestPaths: map[string]string{},
CustomActionJobManifestPaths: map[string]string{},
}
for _, c := range cfgs {
for _, tc := range c.Verify {
if tc.ExecutionMode.KubernetesClusterExecutionMode != nil && tc.ExecutionMode.KubernetesClusterExecutionMode.JobManifestPath != "" {
l.VerifyJobManifestPaths[tc.Name] = tc.ExecutionMode.KubernetesClusterExecutionMode.JobManifestPath
}
// TODO(#8572) add similar logic for customAction schema fields when they are complete
}
}
return formatter.Write(l)
}
204 changes: 204 additions & 0 deletions pkg/skaffold/inspect/jobManifestPaths/list_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
/*
Copyright 2021 The Skaffold Authors
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 inspect

import (
"bytes"
"context"
"errors"
"fmt"
"testing"

"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/config"
"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/inspect"
"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/parser"
sErrors "github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/schema/errors"
"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/schema/latest"
"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/util/stringslice"
"github.com/GoogleContainerTools/skaffold/v2/testutil"
v1 "k8s.io/api/core/v1"
)

var manifest = `apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: leeroy-app
name: leeroy-app
spec:
replicas: 1
selector:
matchLabels:
app: leeroy-app
template:
metadata:
labels:
app: leeroy-app
spec:
containers:
- image: leeroy-app:1d38c165eada98acbbf9f8869b92bf32f4f9c4e80bdea23d20c7020db3ace2da
name: leeroy-app
ports:
- containerPort: 50051
name: http
`

var manifestWithNamespace = `apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: leeroy-app
name: leeroy-app
namespace: manifest-namespace
spec:
replicas: 1
selector:
matchLabels:
app: leeroy-app
template:
metadata:
labels:
app: leeroy-app
spec:
containers:
- image: leeroy-app:1d38c165eada98acbbf9f8869b92bf32f4f9c4e80bdea23d20c7020db3ace2da
name: leeroy-app
ports:
- containerPort: 50051
name: http
`

func TestPrintJobManifestPathsList(t *testing.T) {
tests := []struct {
description string
manifest string
profiles []string
module []string
err error
expected string
}{
{
description: "print all jobManifestPaths where no jobManifestPath is set in the verify config",
manifest: manifest,
expected: "{\"verifyJobManifestPaths\":{},\"customActionJobManifestPaths\":{}}" + "\n",
module: []string{"cfg-without-jobManifestPaths"},
},
{
description: "print all jobManifestPaths where one jobManifestPath is set in the verify config via a profile",
manifest: manifest,
expected: "{\"verifyJobManifestPaths\":{\"foo\":\"foo.yaml\"},\"customActionJobManifestPaths\":{}}" + "\n",
profiles: []string{"has-jobManifestPath"},
module: []string{"cfg-without-jobManifestPaths"},
},
{
description: "print all jobManifestPaths where one jobManifestPath is set in the verify config via a module",
manifest: manifest,
expected: "{\"verifyJobManifestPaths\":{\"bar\":\"bar.yaml\"},\"customActionJobManifestPaths\":{}}" + "\n",
module: []string{"cfg-with-jobManifestPaths"},
},
{
description: "actionable error",
manifest: manifest,
err: sErrors.MainConfigFileNotFoundErr("path/to/skaffold.yaml", fmt.Errorf("failed to read file : %q", "skaffold.yaml")),
expected: `{"errorCode":"CONFIG_FILE_NOT_FOUND_ERR","errorMessage":"unable to find configuration file \"path/to/skaffold.yaml\": failed to read file : \"skaffold.yaml\". Check that the specified configuration file exists at \"path/to/skaffold.yaml\"."}` + "\n",
},
{
description: "generic error",
manifest: manifest,
err: errors.New("some error occurred"),
expected: `{"errorCode":"INSPECT_UNKNOWN_ERR","errorMessage":"some error occurred"}` + "\n",
},
}

for _, test := range tests {
testutil.Run(t, test.description, func(t *testutil.T) {
configSet := parser.SkaffoldConfigSet{
&parser.SkaffoldConfigEntry{SkaffoldConfig: &latest.SkaffoldConfig{
Metadata: latest.Metadata{Name: "cfg-without-jobManifestPaths"},
Pipeline: latest.Pipeline{},
Profiles: []latest.Profile{
{Name: "has-jobManifestPath",
Pipeline: latest.Pipeline{
Verify: []*latest.VerifyTestCase{
{
Name: "foo",
Container: v1.Container{
Name: "foo",
Image: "foo",
},
ExecutionMode: latest.VerifyExecutionModeConfig{
VerifyExecutionModeType: latest.VerifyExecutionModeType{
KubernetesClusterExecutionMode: &latest.KubernetesClusterVerifier{
JobManifestPath: "foo.yaml",
},
},
},
},
},
},
}},
}, SourceFile: "path/to/cfg-without-jobManifestPaths"},

&parser.SkaffoldConfigEntry{SkaffoldConfig: &latest.SkaffoldConfig{
Metadata: latest.Metadata{Name: "cfg-with-jobManifestPaths"},
Pipeline: latest.Pipeline{
Verify: []*latest.VerifyTestCase{
{
Name: "bar",
Container: v1.Container{
Name: "bar",
Image: "bar",
},
ExecutionMode: latest.VerifyExecutionModeConfig{
VerifyExecutionModeType: latest.VerifyExecutionModeType{
KubernetesClusterExecutionMode: &latest.KubernetesClusterVerifier{
JobManifestPath: "bar.yaml",
},
},
},
},
},
},
}, SourceFile: "path/to/cfg-with-default-namespace"},
}
t.Override(&inspect.GetConfigSet, func(_ context.Context, opts config.SkaffoldOptions) (parser.SkaffoldConfigSet, error) {
// mock profile activation
var set parser.SkaffoldConfigSet
for _, c := range configSet {
if len(opts.ConfigurationFilter) > 0 && !stringslice.Contains(opts.ConfigurationFilter, c.Metadata.Name) {
continue
}
for _, pName := range opts.Profiles {
for _, profile := range c.Profiles {
if profile.Name != pName {
continue
}
c.Verify = profile.Verify
}
}
set = append(set, c)
}
return set, test.err
})
var buf bytes.Buffer
err := PrintJobManifestPathsList(context.Background(), &buf, inspect.Options{
OutFormat: "json", Modules: test.module, Profiles: test.profiles})
t.CheckError(test.err != nil, err)
t.CheckDeepEqual(test.expected, buf.String())
})
}
}
Loading

0 comments on commit 3b16ae3

Please sign in to comment.