-
Notifications
You must be signed in to change notification settings - Fork 263
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Add --filename flag to service create command #913
Changes from 1 commit
0218ea0
228610a
bd7ea6b
9a3c1b9
07c1e81
d10ecd1
e99a0e2
d1c8059
5ace3b2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,6 +18,7 @@ import ( | |
"errors" | ||
"fmt" | ||
"io" | ||
"os" | ||
|
||
"knative.dev/client/pkg/kn/commands" | ||
servinglib "knative.dev/client/pkg/serving" | ||
|
@@ -28,6 +29,8 @@ import ( | |
corev1 "k8s.io/api/core/v1" | ||
apierrors "k8s.io/apimachinery/pkg/api/errors" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"k8s.io/apimachinery/pkg/util/yaml" | ||
|
||
servingv1 "knative.dev/serving/pkg/apis/serving/v1" | ||
|
||
clientservingv1 "knative.dev/client/pkg/serving/v1" | ||
|
@@ -79,7 +82,7 @@ func NewServiceCreateCommand(p *commands.KnParams) *cobra.Command { | |
return errors.New("'service create' requires the service name given as single argument") | ||
} | ||
name := args[0] | ||
if editFlags.Image == "" { | ||
if editFlags.Image == "" && editFlags.File == "" { | ||
return errors.New("'service create' requires the image name to run provided with the --image option") | ||
} | ||
|
||
|
@@ -88,7 +91,12 @@ func NewServiceCreateCommand(p *commands.KnParams) *cobra.Command { | |
return err | ||
} | ||
|
||
service, err := constructService(cmd, editFlags, name, namespace) | ||
var service *servingv1.Service | ||
if editFlags.File == "" { | ||
service, err = constructService(cmd, editFlags, name, namespace) | ||
} else { | ||
service, err = constructServiceFromFile(cmd, editFlags, name, namespace) | ||
} | ||
if err != nil { | ||
return err | ||
} | ||
|
@@ -97,8 +105,7 @@ func NewServiceCreateCommand(p *commands.KnParams) *cobra.Command { | |
if err != nil { | ||
return err | ||
} | ||
|
||
serviceExists, err := serviceExists(client, name) | ||
serviceExists, err := serviceExists(client, service.Name) | ||
if err != nil { | ||
return err | ||
} | ||
|
@@ -108,7 +115,7 @@ func NewServiceCreateCommand(p *commands.KnParams) *cobra.Command { | |
if !editFlags.ForceCreate { | ||
return fmt.Errorf( | ||
"cannot create service '%s' in namespace '%s' "+ | ||
"because the service already exists and no --force option was given", name, namespace) | ||
"because the service already exists and no --force option was given", service.Name, namespace) | ||
} | ||
err = replaceService(client, service, waitFlags, out) | ||
} else { | ||
|
@@ -260,3 +267,32 @@ func constructService(cmd *cobra.Command, editFlags ConfigurationEditFlags, name | |
} | ||
return &service, nil | ||
} | ||
|
||
// constructServiceFromFile creates struct from provided file | ||
func constructServiceFromFile(cmd *cobra.Command, editFlags ConfigurationEditFlags, name, namespace string) (*servingv1.Service, error) { | ||
var service servingv1.Service | ||
file, err := os.Open(editFlags.File) | ||
if err != nil { | ||
return nil, err | ||
} | ||
decoder := yaml.NewYAMLOrJSONDecoder(file, 512) | ||
|
||
err = decoder.Decode(&service) | ||
if err != nil { | ||
return nil, err | ||
} | ||
dsimansk marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if service.Name != name { | ||
return nil, fmt.Errorf("Provided service name '%s' doesn't match name from file '%s'.", name, service.Name) | ||
dsimansk marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
// Set namespace in case it's specified as --namespace | ||
service.ObjectMeta.Namespace = namespace | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should the namespace also follow check as done above for service name ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think for the namespace we should just allow an override from the CLI. A typical use case is that you create your service maybe for a different environment in the file but want to apply it here in a different context (that's different from the "name" which is an identifier that does not change across environments). |
||
|
||
// We need to generate revision to have --force replace working | ||
revName, err := servinglib.GenerateRevisionName(editFlags.RevisionName, &service) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am thinking if the client-side revision name generation could be turned off if using files mode? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When I was testing the flag locally I've experienced the above behaviour. When trying to -recreate the service with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes could be, but we should be sure what it actually happen when using For opening an issue for serving we would need to recreate the steps via yaml files, I think. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, the issue is limited to a corner case when the same source is used twice, in other words to replace the same already created same Service. In that case such an update op never replaces the original Service content, causing no set of event to be produced in the cluster for wait to finish without timing out. That kind of issue isn't present in imperative I'd suggest the following:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For a quick win, I would go for the second option. We then can implement that improvement to pick up other options later. wdyt ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK, I'm on board with quick win. :) Anyway it'll better to have improvement PR with proper amount of tests and bake time for options combination. I'll create a new issue for it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Follow-up enhancement issue #923. |
||
if err != nil { | ||
return nil, err | ||
} | ||
Comment on lines
+305
to
+307
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No tests for problem generating revision as I can comment these and pass tests. This is less important coverage than the comment above. |
||
service.Spec.Template.Name = revName | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Where does the override from all other options from the CLI happen ? I mean when someone uses
then wdyt ? As this looks a bit more involved I would be happy to add this in a second PR. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Initially I've left it out, because of potential clashes. However from my limited tests so far it looks well good. E.g. env vars are merged, service account added etc. I'll add a few tests to cover those use cases and update the PR. Plus there's another benefit outlined here. |
||
return &service, nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
// Copyright 2020 The Knative 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. | ||
|
||
// +build e2e | ||
// +build !eventing | ||
|
||
package e2e | ||
|
||
import ( | ||
"fmt" | ||
"io/ioutil" | ||
"path/filepath" | ||
"testing" | ||
|
||
"gotest.tools/assert" | ||
|
||
"knative.dev/client/lib/test" | ||
"knative.dev/client/pkg/util" | ||
) | ||
|
||
const ( | ||
ServiceYAML string = ` | ||
apiVersion: serving.knative.dev/v1 | ||
kind: Service | ||
metadata: | ||
name: foo-yaml | ||
spec: | ||
template: | ||
spec: | ||
containers: | ||
- image: %s | ||
env: | ||
- name: TARGET | ||
value: "Go Sample v1"` | ||
|
||
ServiceJSON string = ` | ||
{ | ||
"apiVersion": "serving.knative.dev/v1", | ||
"kind": "Service", | ||
"metadata": { | ||
"name": "foo-json" | ||
}, | ||
"spec": { | ||
"template": { | ||
"spec": { | ||
"containers": [ | ||
{ | ||
"image": "%s", | ||
"env": [ | ||
{ | ||
"name": "TARGET", | ||
"value": "Go Sample v1" | ||
} | ||
] | ||
} | ||
] | ||
} | ||
} | ||
} | ||
}` | ||
) | ||
|
||
func TestServiceCreateFromFile(t *testing.T) { | ||
t.Parallel() | ||
it, err := test.NewKnTest() | ||
assert.NilError(t, err) | ||
defer func() { | ||
assert.NilError(t, it.Teardown()) | ||
}() | ||
|
||
r := test.NewKnRunResultCollector(t, it) | ||
defer r.DumpIfFailed() | ||
|
||
tempDir, err := ioutil.TempDir("", "kn-file") | ||
dsimansk marked this conversation as resolved.
Show resolved
Hide resolved
|
||
assert.NilError(t, err) | ||
|
||
test.CreateFile("foo.json", fmt.Sprintf(ServiceJSON, test.KnDefaultTestImage), tempDir, test.FileModeReadWrite) | ||
test.CreateFile("foo.yaml", fmt.Sprintf(ServiceYAML, test.KnDefaultTestImage), tempDir, test.FileModeReadWrite) | ||
|
||
t.Log("create foo-json service from JSON file") | ||
serviceCreateFromFile(r, "foo-json", filepath.Join(tempDir, "foo.json")) | ||
|
||
t.Log("create foo-yaml service from YAML file") | ||
serviceCreateFromFile(r, "foo-yaml", filepath.Join(tempDir, "foo.yaml")) | ||
|
||
t.Log("error message for non-existing file") | ||
serviceCreateFromFileNameMismatch(r, "foo", filepath.Join(tempDir, "foo.yaml")) | ||
serviceCreateFromFileNameMismatch(r, "foo", filepath.Join(tempDir, "foo.json")) | ||
} | ||
|
||
func serviceCreateFromFile(r *test.KnRunResultCollector, serviceName, filePath string) { | ||
out := r.KnTest().Kn().Run("service", "create", serviceName, "--file", filePath) | ||
r.AssertNoError(out) | ||
assert.Check(r.T(), util.ContainsAllIgnoreCase(out.Stdout, "service", serviceName, "creating", "namespace", r.KnTest().Kn().Namespace(), "ready")) | ||
|
||
out = r.KnTest().Kn().Run("service", "describe", serviceName, "--verbose") | ||
r.AssertNoError(out) | ||
assert.Check(r.T(), util.ContainsAllIgnoreCase(out.Stdout, serviceName)) | ||
} | ||
|
||
func serviceCreateFromFileError(r *test.KnRunResultCollector, serviceName, filePath string) { | ||
out := r.KnTest().Kn().Run("service", "create", serviceName, "--file", filePath) | ||
r.AssertError(out) | ||
assert.Check(r.T(), util.ContainsAllIgnoreCase(out.Stderr, "no", "such", "file", "directory", filePath)) | ||
} | ||
|
||
func serviceCreateFromFileNameMismatch(r *test.KnRunResultCollector, serviceName, filePath string) { | ||
out := r.KnTest().Kn().Run("service", "create", serviceName, "--file", filePath) | ||
r.AssertError(out) | ||
assert.Check(r.T(), util.ContainsAllIgnoreCase(out.Stderr, "provided", "'"+serviceName+"'", "name", "match", "from", "file")) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I always debate using
Filename
instead ofFile
since there is invariably aFile
object created somewhere later. Of course,File
is shorter as a flag name so makes sense too... Argh.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Following the
kubectl
example we could settle on-f, --filename
as--force
is not using that shorthand.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed the name in 9a3c1b9.