func resourceForStrategyType(strategy buildapi.BuildStrategy) unversioned.GroupResource { switch { case strategy.DockerStrategy != nil: return buildapi.Resource(authorizationapi.DockerBuildResource) case strategy.CustomStrategy != nil: return buildapi.Resource(authorizationapi.CustomBuildResource) case strategy.SourceStrategy != nil: return buildapi.Resource(authorizationapi.SourceBuildResource) } return unversioned.GroupResource{} }
func resourceForStrategyType(strategy buildapi.BuildStrategy) (unversioned.GroupResource, error) { switch { case strategy.DockerStrategy != nil: return buildapi.Resource(authorizationapi.DockerBuildResource), nil case strategy.CustomStrategy != nil: return buildapi.Resource(authorizationapi.CustomBuildResource), nil case strategy.SourceStrategy != nil: return buildapi.Resource(authorizationapi.SourceBuildResource), nil case strategy.JenkinsPipelineStrategy != nil: return buildapi.Resource(authorizationapi.JenkinsPipelineBuildResource), nil default: return unversioned.GroupResource{}, fmt.Errorf("unrecognized build strategy: %#v", strategy) } }
// NewStorage returns a RESTStorage object that will work against Build objects. func NewREST(s storage.Interface) (*REST, *DetailsREST) { prefix := "/builds" store := &etcdgeneric.Etcd{ NewFunc: func() runtime.Object { return &api.Build{} }, NewListFunc: func() runtime.Object { return &api.BuildList{} }, QualifiedResource: api.Resource("builds"), KeyRootFunc: func(ctx kapi.Context) string { return etcdgeneric.NamespaceKeyRootFunc(ctx, prefix) }, KeyFunc: func(ctx kapi.Context, id string) (string, error) { return etcdgeneric.NamespaceKeyFunc(ctx, prefix, id) }, ObjectNameFunc: func(obj runtime.Object) (string, error) { return obj.(*api.Build).Name, nil }, PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher { return build.Matcher(label, field) }, CreateStrategy: build.Strategy, UpdateStrategy: build.Strategy, DeleteStrategy: build.Strategy, Decorator: build.Decorator, ReturnDeletedObject: false, Storage: s, } detailsStore := *store detailsStore.UpdateStrategy = build.DetailsStrategy return &REST{store}, &DetailsREST{&detailsStore} }
// NewStorage returns a RESTStorage object that will work against Build objects. func NewREST(optsGetter restoptions.Getter) (*REST, *DetailsREST, error) { prefix := "/builds" store := ®istry.Store{ NewFunc: func() runtime.Object { return &api.Build{} }, NewListFunc: func() runtime.Object { return &api.BuildList{} }, QualifiedResource: api.Resource("builds"), KeyRootFunc: func(ctx kapi.Context) string { return registry.NamespaceKeyRootFunc(ctx, prefix) }, KeyFunc: func(ctx kapi.Context, id string) (string, error) { return registry.NamespaceKeyFunc(ctx, prefix, id) }, ObjectNameFunc: func(obj runtime.Object) (string, error) { return obj.(*api.Build).Name, nil }, PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher { return build.Matcher(label, field) }, CreateStrategy: build.Strategy, UpdateStrategy: build.Strategy, DeleteStrategy: build.Strategy, Decorator: build.Decorator, ReturnDeletedObject: false, } if err := restoptions.ApplyOptions(optsGetter, store, prefix); err != nil { return nil, nil, err } detailsStore := *store detailsStore.UpdateStrategy = build.DetailsStrategy return &REST{store}, &DetailsREST{&detailsStore}, nil }
// NewREST returns a RESTStorage object that will work against Build objects. func NewREST(optsGetter restoptions.Getter) (*REST, *DetailsREST, error) { store := ®istry.Store{ NewFunc: func() runtime.Object { return &api.Build{} }, NewListFunc: func() runtime.Object { return &api.BuildList{} }, QualifiedResource: api.Resource("builds"), ObjectNameFunc: func(obj runtime.Object) (string, error) { return obj.(*api.Build).Name, nil }, PredicateFunc: func(label labels.Selector, field fields.Selector) *generic.SelectionPredicate { return build.Matcher(label, field) }, CreateStrategy: build.Strategy, UpdateStrategy: build.Strategy, DeleteStrategy: build.Strategy, Decorator: build.Decorator, ReturnDeletedObject: false, } if err := restoptions.ApplyOptions(optsGetter, store, true, storage.NoTriggerPublisher); err != nil { return nil, nil, err } detailsStore := *store detailsStore.UpdateStrategy = build.DetailsStrategy return &REST{store}, &DetailsREST{&detailsStore}, nil }
func (r *BuildConfigRegistry) GetBuildConfig(ctx kapi.Context, id string) (*api.BuildConfig, error) { r.Lock() defer r.Unlock() if r.BuildConfig != nil && r.BuildConfig.Name == id { return r.BuildConfig, r.Err } return nil, kapierrors.NewNotFound(api.Resource("buildconfig"), id) }
// ServeHTTP implements rest.HookHandler func (w *WebHook) ServeHTTP(writer http.ResponseWriter, req *http.Request, ctx kapi.Context, name, subpath string) error { parts := strings.Split(subpath, "/") if len(parts) != 2 { return errors.NewBadRequest(fmt.Sprintf("unexpected hook subpath %s", subpath)) } secret, hookType := parts[0], parts[1] plugin, ok := w.plugins[hookType] if !ok { return errors.NewNotFound(buildapi.Resource("buildconfighook"), hookType) } config, err := w.registry.GetBuildConfig(ctx, name) if err != nil { // clients should not be able to find information about build configs in // the system unless the config exists and the secret matches return errors.NewUnauthorized(fmt.Sprintf("the webhook %q for %q did not accept your secret", hookType, name)) } revision, envvars, proceed, err := plugin.Extract(config, secret, "", req) if !proceed { switch err { case webhook.ErrSecretMismatch, webhook.ErrHookNotEnabled: return errors.NewUnauthorized(fmt.Sprintf("the webhook %q for %q did not accept your secret", hookType, name)) case webhook.MethodNotSupported: return errors.NewMethodNotSupported(buildapi.Resource("buildconfighook"), req.Method) } if _, ok := err.(*errors.StatusError); !ok && err != nil { return errors.NewInternalError(fmt.Errorf("hook failed: %v", err)) } return err } warning := err buildTriggerCauses := generateBuildTriggerInfo(revision, hookType, secret) request := &buildapi.BuildRequest{ TriggeredBy: buildTriggerCauses, ObjectMeta: kapi.ObjectMeta{Name: name}, Revision: revision, Env: envvars, } if _, err := w.instantiator.Instantiate(config.Namespace, request); err != nil { return errors.NewInternalError(fmt.Errorf("could not generate a build: %v", err)) } return warning }
// Get retrieves the Build from the indexer for a given namespace and name. func (s buildNamespaceLister) Get(name string) (*api.Build, error) { obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) if err != nil { return nil, err } if !exists { return nil, errors.NewNotFound(api.Resource("build"), name) } return obj.(*api.Build), nil }
// Get the build config matching the name from the cache. func (s storeBuildConfigsNamespacer) Get(name string) (*buildapi.BuildConfig, error) { obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) if err != nil { return nil, err } if !exists { return nil, kapierrors.NewNotFound(buildapi.Resource("buildconfigs"), name) } return obj.(*buildapi.BuildConfig), nil }
// createBuild is responsible for validating build object and saving it and returning newly created object func (g *BuildGenerator) createBuild(ctx kapi.Context, build *buildapi.Build) (*buildapi.Build, error) { if !kapi.ValidNamespace(ctx, &build.ObjectMeta) { return nil, errors.NewConflict(buildapi.Resource("build"), build.Namespace, fmt.Errorf("Build.Namespace does not match the provided context")) } kapi.FillObjectMetaSystemFields(ctx, &build.ObjectMeta) err := g.Client.CreateBuild(ctx, build) if err != nil { return nil, err } return g.Client.GetBuild(ctx, build.Name) }
func TestBuildConfigUpdateError(t *testing.T) { // valid configuration, but build creation fails, in that situation the buildconfig should not be updated buildcfg := mockBuildConfig("registry.com/namespace/imagename", "registry.com/namespace/imagename", "testImageStream", "testTag") imageStream := mockImageStream("testImageStream", "registry.com/namespace/imagename", map[string]string{"testTag": "newImageID123"}) image := mockImage("testImage@id", "registry.com/namespace/imagename@id") controller := mockImageChangeController(buildcfg, imageStream, image) bcInstantiator := controller.BuildConfigInstantiator.(*buildConfigInstantiator) bcUpdater := bcInstantiator.buildConfigUpdater bcUpdater.err = kerrors.NewConflict(buildapi.Resource("BuildConfig"), buildcfg.Name, errors.New("foo")) err := controller.HandleImageRepo(imageStream) if len(bcInstantiator.name) == 0 { t.Error("Expected build generation when new image was created!") } if err == nil || !strings.Contains(err.Error(), "will be retried") { t.Fatalf("Expected 'will be retried' from HandleImageRepo, got %s", err.Error()) } }
// ServeHTTP implements rest.HookHandler func (c *controller) ServeHTTP(w http.ResponseWriter, req *http.Request, ctx kapi.Context, name, subpath string) error { parts := strings.Split(subpath, "/") if len(parts) < 2 { return errors.NewBadRequest(fmt.Sprintf("unexpected hook subpath %s", subpath)) } secret, hookType := parts[0], parts[1] plugin, ok := c.plugins[hookType] if !ok { return errors.NewNotFound(buildapi.Resource("buildconfighook"), hookType) } config, err := c.registry.GetBuildConfig(ctx, name) if err != nil { // clients should not be able to find information about build configs in the system unless the config exists // and the secret matches return errors.NewUnauthorized(fmt.Sprintf("the webhook %q for %q did not accept your secret", hookType, name)) } revision, proceed, err := plugin.Extract(config, secret, "", req) switch err { case webhook.ErrSecretMismatch, webhook.ErrHookNotEnabled: return errors.NewUnauthorized(fmt.Sprintf("the webhook %q for %q did not accept your secret", hookType, name)) case nil: default: return errors.NewInternalError(fmt.Errorf("hook failed: %v", err)) } if !proceed { return nil } request := &buildapi.BuildRequest{ ObjectMeta: kapi.ObjectMeta{Name: name}, Revision: revision, } if _, err := c.instantiator.Instantiate(config.Namespace, request); err != nil { return errors.NewInternalError(fmt.Errorf("could not generate a build: %v", err)) } return nil }
// NewREST returns a RESTStorage object that will work against BuildConfig. func NewREST(optsGetter restoptions.Getter) (*REST, error) { store := ®istry.Store{ NewFunc: func() runtime.Object { return &api.BuildConfig{} }, NewListFunc: func() runtime.Object { return &api.BuildConfigList{} }, QualifiedResource: api.Resource("buildconfigs"), ObjectNameFunc: func(obj runtime.Object) (string, error) { return obj.(*api.BuildConfig).Name, nil }, PredicateFunc: func(label labels.Selector, field fields.Selector) storage.SelectionPredicate { return buildconfig.Matcher(label, field) }, CreateStrategy: buildconfig.Strategy, UpdateStrategy: buildconfig.Strategy, DeleteStrategy: buildconfig.Strategy, ReturnDeletedObject: false, } if err := restoptions.ApplyOptions(optsGetter, store, true, storage.NoTriggerPublisher); err != nil { return nil, err } return &REST{store}, nil }
func removeBuildStrategyRoleResources(t *testing.T, clusterAdminClient, projectAdminClient, projectEditorClient *client.Client) { // remove resources from role so that certain build strategies are forbidden removeBuildStrategyPrivileges(t, clusterAdminClient.ClusterRoles(), bootstrappolicy.EditRoleName) if err := testutil.WaitForPolicyUpdate(projectEditorClient, testutil.Namespace(), "create", buildapi.Resource(authorizationapi.DockerBuildResource), false); err != nil { t.Error(err) } removeBuildStrategyPrivileges(t, clusterAdminClient.ClusterRoles(), bootstrappolicy.AdminRoleName) if err := testutil.WaitForPolicyUpdate(projectAdminClient, testutil.Namespace(), "create", buildapi.Resource(authorizationapi.DockerBuildResource), false); err != nil { t.Error(err) } }
func TestClusterReaderCoverage(t *testing.T) { testutil.RequireEtcd(t) defer testutil.DumpEtcdOnFailure(t) _, clusterAdminKubeConfig, err := testserver.StartTestMasterAPI() if err != nil { t.Fatalf("unexpected error: %v", err) } clusterAdminClient, err := testutil.GetClusterAdminClient(clusterAdminKubeConfig) if err != nil { t.Fatalf("unexpected error: %v", err) } discoveryClient := client.NewDiscoveryClient(clusterAdminClient.RESTClient) // (map[string]*unversioned.APIResourceList, error) allResourceList, err := discoveryClient.ServerResources() if err != nil { t.Fatalf("unexpected error: %v", err) } allResources := map[unversioned.GroupResource]bool{} for _, resources := range allResourceList { version, err := unversioned.ParseGroupVersion(resources.GroupVersion) if err != nil { t.Fatalf("unexpected error: %v", err) } for _, resource := range resources.APIResources { allResources[version.WithResource(resource.Name).GroupResource()] = true } } escalatingResources := map[unversioned.GroupResource]bool{ oauthapi.Resource("oauthauthorizetokens"): true, oauthapi.Resource("oauthaccesstokens"): true, oauthapi.Resource("oauthclients"): true, imageapi.Resource("imagestreams/secrets"): true, kapi.Resource("secrets"): true, kapi.Resource("pods/exec"): true, kapi.Resource("pods/proxy"): true, kapi.Resource("pods/portforward"): true, kapi.Resource("nodes/proxy"): true, kapi.Resource("services/proxy"): true, } readerRole, err := clusterAdminClient.ClusterRoles().Get(bootstrappolicy.ClusterReaderRoleName) if err != nil { t.Fatalf("unexpected error: %v", err) } for _, rule := range readerRole.Rules { for _, group := range rule.APIGroups { for resource := range rule.Resources { gr := unversioned.GroupResource{Group: group, Resource: resource} if escalatingResources[gr] { t.Errorf("cluster-reader role has escalating resource %v. Check pkg/cmd/server/bootstrappolicy/policy.go.", gr) } delete(allResources, gr) } } } // remove escalating resources that cluster-reader should not have access to for resource := range escalatingResources { delete(allResources, resource) } // remove resources without read APIs nonreadingResources := []unversioned.GroupResource{ buildapi.Resource("buildconfigs/instantiatebinary"), buildapi.Resource("buildconfigs/instantiate"), buildapi.Resource("builds/clone"), deployapi.Resource("deploymentconfigrollbacks"), deployapi.Resource("generatedeploymentconfigs"), deployapi.Resource("deploymentconfigs/rollback"), imageapi.Resource("imagestreamimports"), imageapi.Resource("imagestreammappings"), extensionsapi.Resource("deployments/rollback"), kapi.Resource("pods/attach"), kapi.Resource("namespaces/finalize"), } for _, resource := range nonreadingResources { delete(allResources, resource) } // anything left in the map is missing from the permissions if len(allResources) > 0 { t.Errorf("cluster-reader role is missing %v. Check pkg/cmd/server/bootstrappolicy/policy.go.", allResources) } }
func TestStop(t *testing.T) { notFound := func() runtime.Object { return &(kerrors.NewNotFound(buildapi.Resource("BuildConfig"), configName).ErrStatus) } tests := map[string]struct { targetBC string oc *testclient.Fake expected []ktestclient.Action err bool }{ "simple stop": { targetBC: configName, oc: newBuildListFake(makeBuildConfig(configName, 0, false)), expected: []ktestclient.Action{ ktestclient.NewGetAction("buildconfigs", "default", configName), // Since there are no builds associated with this build config, do not expect an update ktestclient.NewListAction("builds", "default", kapi.ListOptions{LabelSelector: buildutil.BuildConfigSelector(configName)}), ktestclient.NewListAction("builds", "default", kapi.ListOptions{LabelSelector: buildutil.BuildConfigSelectorDeprecated(configName)}), ktestclient.NewDeleteAction("buildconfigs", "default", configName), }, err: false, }, "multiple builds": { targetBC: configName, oc: newBuildListFake(makeBuildConfig(configName, 4, false), makeBuildList(configName, 4)), expected: []ktestclient.Action{ ktestclient.NewGetAction("buildconfigs", "default", configName), ktestclient.NewListAction("builds", "default", kapi.ListOptions{LabelSelector: buildutil.BuildConfigSelector(configName)}), ktestclient.NewListAction("builds", "default", kapi.ListOptions{LabelSelector: buildutil.BuildConfigSelectorDeprecated(configName)}), ktestclient.NewGetAction("buildconfigs", "default", configName), // Second GET to enable conflict retry logic ktestclient.NewUpdateAction("buildconfigs", "default", makeBuildConfig(configName, 4, true)), // Because this bc has builds, it is paused ktestclient.NewDeleteAction("builds", "default", "build-1"), ktestclient.NewDeleteAction("builds", "default", "build-2"), ktestclient.NewDeleteAction("builds", "default", "build-3"), ktestclient.NewDeleteAction("builds", "default", "build-4"), ktestclient.NewDeleteAction("buildconfigs", "default", configName), }, err: false, }, "long name builds": { targetBC: longConfigNameA, oc: newBuildListFake(makeBuildConfig(longConfigNameA, 4, false), makeBuildList(longConfigNameA, 4), makeBuildList(longConfigNameB, 4)), expected: []ktestclient.Action{ ktestclient.NewGetAction("buildconfigs", "default", longConfigNameA), ktestclient.NewListAction("builds", "default", kapi.ListOptions{LabelSelector: buildutil.BuildConfigSelector(longConfigNameA)}), ktestclient.NewListAction("builds", "default", kapi.ListOptions{LabelSelector: buildutil.BuildConfigSelectorDeprecated(longConfigNameA)}), ktestclient.NewGetAction("buildconfigs", "default", longConfigNameA), // Second GET to enable conflict retry logic ktestclient.NewUpdateAction("buildconfigs", "default", makeBuildConfig(longConfigNameA, 4, true)), // Because this bc has builds, it is paused ktestclient.NewDeleteAction("builds", "default", "build-1"), ktestclient.NewDeleteAction("builds", "default", "build-2"), ktestclient.NewDeleteAction("builds", "default", "build-3"), ktestclient.NewDeleteAction("builds", "default", "build-4"), ktestclient.NewDeleteAction("buildconfigs", "default", longConfigNameA), }, err: false, }, "no config, no or some builds": { targetBC: configName, oc: testclient.NewSimpleFake(notFound(), makeBuildList(configName, 2)), expected: []ktestclient.Action{ ktestclient.NewGetAction("buildconfigs", "default", configName), }, err: true, }, "config, no builds": { targetBC: configName, oc: testclient.NewSimpleFake(makeBuildConfig(configName, 0, false)), expected: []ktestclient.Action{ ktestclient.NewGetAction("buildconfigs", "default", configName), ktestclient.NewListAction("builds", "default", kapi.ListOptions{LabelSelector: buildutil.BuildConfigSelector(configName)}), ktestclient.NewListAction("builds", "default", kapi.ListOptions{LabelSelector: buildutil.BuildConfigSelectorDeprecated(configName)}), ktestclient.NewDeleteAction("buildconfigs", "default", configName), }, err: false, }, } for testName, test := range tests { reaper := &BuildConfigReaper{oc: test.oc, pollInterval: time.Millisecond, timeout: time.Millisecond} err := reaper.Stop("default", test.targetBC, 1*time.Second, nil) if !test.err && err != nil { t.Errorf("%s: unexpected error: %v", testName, err) } if test.err && err == nil { t.Errorf("%s: expected an error", testName) } if len(test.oc.Actions()) != len(test.expected) { t.Fatalf("%s: unexpected actions: %v, expected %v", testName, test.oc.Actions(), test.expected) } for j, actualAction := range test.oc.Actions() { if !actionsAreEqual(actualAction, test.expected[j]) { t.Errorf("%s: unexpected action: %v, expected %v", testName, actualAction, test.expected[j]) } } } }
// Complete calls the upstream Complete for the logs command and then resolves the // resource a user requested to view its logs and creates the appropriate logOptions // object for it. func (o *OpenShiftLogsOptions) Complete(f *clientcmd.Factory, out io.Writer, cmd *cobra.Command, args []string) error { if err := o.KubeLogOptions.Complete(f.Factory, out, cmd, args); err != nil { return err } namespace, _, err := f.DefaultNamespace() if err != nil { return err } podLogOptions := o.KubeLogOptions.Options.(*kapi.PodLogOptions) mapper, typer := f.Object() infos, err := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), kapi.Codecs.UniversalDecoder()). NamespaceParam(namespace).DefaultNamespace(). ResourceNames("pods", args...). SingleResourceType().RequireObject(false). Do().Infos() if err != nil { return err } if len(infos) != 1 { return errors.New("expected a resource") } version := kcmdutil.GetFlagInt64(cmd, "version") _, resource := meta.KindToResource(infos[0].Mapping.GroupVersionKind, false) // TODO: podLogOptions should be included in our own logOptions objects. switch resource.GroupResource() { case buildapi.Resource("build"), buildapi.Resource("buildconfig"): bopts := &buildapi.BuildLogOptions{ Follow: podLogOptions.Follow, Previous: podLogOptions.Previous, SinceSeconds: podLogOptions.SinceSeconds, SinceTime: podLogOptions.SinceTime, Timestamps: podLogOptions.Timestamps, TailLines: podLogOptions.TailLines, LimitBytes: podLogOptions.LimitBytes, } if version != 0 { bopts.Version = &version } o.Options = bopts case deployapi.Resource("deploymentconfig"): dopts := &deployapi.DeploymentLogOptions{ Follow: podLogOptions.Follow, Previous: podLogOptions.Previous, SinceSeconds: podLogOptions.SinceSeconds, SinceTime: podLogOptions.SinceTime, Timestamps: podLogOptions.Timestamps, TailLines: podLogOptions.TailLines, LimitBytes: podLogOptions.LimitBytes, } if version != 0 { dopts.Version = &version } o.Options = dopts default: o.Options = nil } return nil }
client client.Interface } var _ = oadmission.WantsOpenshiftClient(&buildByStrategy{}) var _ = oadmission.Validator(&buildByStrategy{}) // NewBuildByStrategy returns an admission control for builds that checks // on policy based on the build strategy type func NewBuildByStrategy() admission.Interface { return &buildByStrategy{ Handler: admission.NewHandler(admission.Create, admission.Update), } } var ( buildsResource = buildapi.Resource("builds") buildConfigsResource = buildapi.Resource("buildconfigs") ) func (a *buildByStrategy) Admit(attr admission.Attributes) error { if resource := attr.GetResource().GroupResource(); resource != buildsResource && resource != buildConfigsResource { return nil } // Explicitly exclude the builds/details subresource because it's only // updating commit info and cannot change build type. if attr.GetResource().GroupResource() == buildsResource && attr.GetSubresource() == "details" { return nil } switch obj := attr.GetObject().(type) { case *buildapi.Build: return a.checkBuildAuthorization(obj, attr)
func setupBuildStrategyTest(t *testing.T, includeControllers bool) (clusterAdminClient, projectAdminClient, projectEditorClient *client.Client) { testutil.RequireEtcd(t) namespace := testutil.Namespace() var clusterAdminKubeConfig string var err error if includeControllers { _, clusterAdminKubeConfig, err = testserver.StartTestMaster() } else { _, clusterAdminKubeConfig, err = testserver.StartTestMasterAPI() } if err != nil { t.Fatalf("unexpected error: %v", err) } clusterAdminClient, err = testutil.GetClusterAdminClient(clusterAdminKubeConfig) if err != nil { t.Fatalf("unexpected error: %v", err) } clusterAdminClientConfig, err := testutil.GetClusterAdminClientConfig(clusterAdminKubeConfig) if err != nil { t.Fatalf("unexpected error: %v", err) } projectAdminClient, err = testserver.CreateNewProject(clusterAdminClient, *clusterAdminClientConfig, namespace, "harold") if err != nil { t.Fatalf("unexpected error: %v", err) } projectEditorClient, _, _, err = testutil.GetClientForUser(*clusterAdminClientConfig, "joe") if err != nil { t.Fatalf("unexpected error: %v", err) } addJoe := &policy.RoleModificationOptions{ RoleNamespace: "", RoleName: bootstrappolicy.EditRoleName, RoleBindingAccessor: policy.NewLocalRoleBindingAccessor(namespace, projectAdminClient), Users: []string{"joe"}, } if err := addJoe.AddRole(); err != nil { t.Fatalf("unexpected error: %v", err) } if err := testutil.WaitForPolicyUpdate(projectEditorClient, namespace, "create", buildapi.Resource(authorizationapi.DockerBuildResource), true); err != nil { t.Fatalf(err.Error()) } // Create builder image stream and tag imageStream := &imageapi.ImageStream{} imageStream.Name = "builderimage" _, err = clusterAdminClient.ImageStreams(testutil.Namespace()).Create(imageStream) if err != nil { t.Fatalf("Couldn't create ImageStream: %v", err) } // Create image stream mapping imageStreamMapping := &imageapi.ImageStreamMapping{} imageStreamMapping.Name = "builderimage" imageStreamMapping.Tag = "latest" imageStreamMapping.Image.Name = "image-id" imageStreamMapping.Image.DockerImageReference = "test/builderimage:latest" err = clusterAdminClient.ImageStreamMappings(testutil.Namespace()).Create(imageStreamMapping) if err != nil { t.Fatalf("Couldn't create ImageStreamMapping: %v", err) } template, err := testutil.GetTemplateFixture("../../examples/jenkins/jenkins-ephemeral-template.json") if err != nil { t.Fatalf("unexpected error: %v", err) } template.Name = "jenkins" template.Namespace = "openshift" _, err = clusterAdminClient.Templates("openshift").Create(template) if err != nil { t.Fatalf("Couldn't create jenkins template: %v", err) } return }
// Complete completes all the required options. func (o *CancelBuildOptions) Complete(f *clientcmd.Factory, cmd *cobra.Command, args []string, in io.Reader, out io.Writer) error { o.In = in o.Out = out o.ErrOut = cmd.OutOrStderr() o.ReportError = func(err error) { o.HasError = true fmt.Fprintf(o.ErrOut, "error: %s\n", err.Error()) } if len(args) == 0 { return kcmdutil.UsageError(cmd, "Must pass a name of a build or a buildconfig to cancel") } namespace, _, err := f.DefaultNamespace() if err != nil { return err } if len(o.States) == 0 { // If --state is not specified, set the default to "new", "pending" and // "running". o.States = []string{"new", "pending", "running"} } else { for _, state := range o.States { if len(state) > 0 && !isStateCancellable(state) { return kcmdutil.UsageError(cmd, "The '--state' flag has invalid value. Must be one of 'new', 'pending', or 'running'") } } } client, _, err := f.Clients() if err != nil { return err } o.Namespace = namespace o.Client = client o.BuildLister = buildclient.NewOSClientBuildClient(client) o.BuildClient = client.Builds(namespace) o.Mapper, _ = f.Object(false) for _, item := range args { resource, name, err := cmdutil.ResolveResource(buildapi.Resource("builds"), item, o.Mapper) if err != nil { return err } switch resource { case buildapi.Resource("buildconfigs"): list, err := buildutil.BuildConfigBuilds(o.BuildLister, o.Namespace, name, nil) if err != nil { return err } for _, b := range list.Items { o.BuildNames = append(o.BuildNames, b.Name) } case buildapi.Resource("builds"): o.BuildNames = append(o.BuildNames, strings.TrimSpace(name)) default: return fmt.Errorf("invalid resource provided: %v", resource) } } return nil }
// Stop deletes the build configuration and all of the associated builds. func (reaper *BuildConfigReaper) Stop(namespace, name string, timeout time.Duration, gracePeriod *kapi.DeleteOptions) error { noBcFound := false // Add deletion pending annotation to the build config err := unversioned.RetryOnConflict(unversioned.DefaultRetry, func() error { bc, err := reaper.oc.BuildConfigs(namespace).Get(name) if kerrors.IsNotFound(err) { noBcFound = true return nil } if err != nil { return err } // Ignore if the annotation already exists if strings.ToLower(bc.Annotations[buildapi.BuildConfigPausedAnnotation]) == "true" { return nil } // Set the annotation and update if err := util.AddObjectAnnotations(bc, map[string]string{buildapi.BuildConfigPausedAnnotation: "true"}); err != nil { return err } _, err = reaper.oc.BuildConfigs(namespace).Update(bc) return err }) if err != nil { return err } if noBcFound { return kerrors.NewNotFound(buildapi.Resource("buildconfig"), name) } // Warn the user if the BuildConfig won't get deleted after this point. bcDeleted := false defer func() { if !bcDeleted { glog.Warningf("BuildConfig %s/%s will not be deleted because not all associated builds could be deleted. You can try re-running the command or removing them manually", namespace, name) } }() // Collect builds related to the config. builds, err := reaper.oc.Builds(namespace).List(kapi.ListOptions{LabelSelector: buildutil.BuildConfigSelector(name)}) if err != nil { return err } errList := []error{} for _, build := range builds.Items { if build.Annotations != nil { if bcName, ok := build.Annotations[buildapi.BuildConfigAnnotation]; ok { // The annotation, if present, has the full build config name. // Check it before proceeding. if bcName != name { continue } } } if err := reaper.oc.Builds(namespace).Delete(build.Name); err != nil { glog.Warningf("Cannot delete Build %s/%s: %v", build.Namespace, build.Name, err) if !kerrors.IsNotFound(err) { errList = append(errList, err) } } } // Collect deprecated builds related to the config. // TODO: Delete this block after BuildConfigLabelDeprecated is removed. builds, err = reaper.oc.Builds(namespace).List(kapi.ListOptions{LabelSelector: buildutil.BuildConfigSelectorDeprecated(name)}) if err != nil { return err } for _, build := range builds.Items { if err := reaper.oc.Builds(namespace).Delete(build.Name); err != nil { glog.Warningf("Cannot delete Build %s/%s: %v", build.Namespace, build.Name, err) if !kerrors.IsNotFound(err) { errList = append(errList, err) } } } // Aggregate all errors if len(errList) > 0 { return kutilerrors.NewAggregate(errList) } // Finally we can delete the BuildConfig if !noBcFound { if err := reaper.oc.BuildConfigs(namespace).Delete(name); err != nil { return err } } bcDeleted = true return nil }
func removeBuildStrategyRoleResources(t *testing.T, clusterAdminClient, projectAdminClient, projectEditorClient *client.Client) { // remove resources from role so that certain build strategies are forbidden for _, role := range []string{bootstrappolicy.BuildStrategyCustomRoleName, bootstrappolicy.BuildStrategyDockerRoleName, bootstrappolicy.BuildStrategySourceRoleName, bootstrappolicy.BuildStrategyJenkinsPipelineRoleName} { options := &policy.RoleModificationOptions{ RoleNamespace: "", RoleName: role, RoleBindingAccessor: policy.NewClusterRoleBindingAccessor(clusterAdminClient), Groups: []string{"system:authenticated"}, } if err := options.RemoveRole(); err != nil { t.Fatalf("unexpected error: %v", err) } } if err := testutil.WaitForPolicyUpdate(projectEditorClient, testutil.Namespace(), "create", buildapi.Resource(authorizationapi.DockerBuildResource), false); err != nil { t.Fatal(err) } if err := testutil.WaitForPolicyUpdate(projectEditorClient, testutil.Namespace(), "create", buildapi.Resource(authorizationapi.SourceBuildResource), false); err != nil { t.Fatal(err) } if err := testutil.WaitForPolicyUpdate(projectEditorClient, testutil.Namespace(), "create", buildapi.Resource(authorizationapi.CustomBuildResource), false); err != nil { t.Fatal(err) } if err := testutil.WaitForPolicyUpdate(projectEditorClient, testutil.Namespace(), "create", buildapi.Resource(authorizationapi.JenkinsPipelineBuildResource), false); err != nil { t.Fatal(err) } }
func grantRestrictedBuildStrategyRoleResources(t *testing.T, clusterAdminClient, projectAdminClient, projectEditorClient *client.Client) { // grant resources to role so that restricted build strategies are available for _, role := range []string{bootstrappolicy.BuildStrategyCustomRoleName} { options := &policy.RoleModificationOptions{ RoleNamespace: "", RoleName: role, RoleBindingAccessor: policy.NewClusterRoleBindingAccessor(clusterAdminClient), Groups: []string{"system:authenticated"}, } if err := options.AddRole(); err != nil { t.Fatalf("unexpected error: %v", err) } } if err := testutil.WaitForPolicyUpdate(projectEditorClient, testutil.Namespace(), "create", buildapi.Resource(authorizationapi.CustomBuildResource), true); err != nil { t.Fatal(err) } }
func setupBuildStrategyTest(t *testing.T, includeControllers bool) (clusterAdminClient, projectAdminClient, projectEditorClient *client.Client) { testutil.RequireEtcd(t) namespace := testutil.Namespace() var clusterAdminKubeConfig string var err error if includeControllers { _, clusterAdminKubeConfig, err = testserver.StartTestMaster() } else { _, clusterAdminKubeConfig, err = testserver.StartTestMasterAPI() } if err != nil { t.Fatalf("unexpected error: %v", err) } clusterAdminClient, err = testutil.GetClusterAdminClient(clusterAdminKubeConfig) if err != nil { t.Fatalf("unexpected error: %v", err) } clusterAdminClientConfig, err := testutil.GetClusterAdminClientConfig(clusterAdminKubeConfig) if err != nil { t.Fatalf("unexpected error: %v", err) } projectAdminClient, err = testserver.CreateNewProject(clusterAdminClient, *clusterAdminClientConfig, namespace, "harold") if err != nil { t.Fatalf("unexpected error: %v", err) } projectEditorClient, _, _, err = testutil.GetClientForUser(*clusterAdminClientConfig, "joe") if err != nil { t.Fatalf("unexpected error: %v", err) } addJoe := &policy.RoleModificationOptions{ RoleNamespace: "", RoleName: bootstrappolicy.EditRoleName, RoleBindingAccessor: policy.NewLocalRoleBindingAccessor(namespace, projectAdminClient), Users: []string{"joe"}, } if err := addJoe.AddRole(); err != nil { t.Fatalf("unexpected error: %v", err) } if err := testutil.WaitForPolicyUpdate(projectEditorClient, namespace, "create", buildapi.Resource(authorizationapi.DockerBuildResource), true); err != nil { t.Fatalf(err.Error()) } // we need a template that doesn't create service accounts or rolebindings so editors can create // pipeline buildconfig's successfully, so we're not using the standard jenkins template. // but we do need a template that creates a service named jenkins. template, err := testutil.GetTemplateFixture("../../examples/jenkins/master-slave/jenkins-master-template.json") if err != nil { t.Fatalf("unexpected error: %v", err) } // pipeline defaults expect to find a template named jenkins-ephemeral // in the openshift namespace. template.Name = "jenkins-ephemeral" template.Namespace = "openshift" _, err = clusterAdminClient.Templates("openshift").Create(template) if err != nil { t.Fatalf("Couldn't create jenkins template: %v", err) } return }
func (o *StartBuildOptions) Complete(f *clientcmd.Factory, in io.Reader, out io.Writer, cmd *cobra.Command, args []string) error { o.In = in o.Out = out o.ErrOut = cmd.Out() o.Git = git.NewRepository() o.ClientConfig = f.OpenShiftClientConfig webhook := o.FromWebhook buildName := o.FromBuild fromFile := o.FromFile fromDir := o.FromDir fromRepo := o.FromRepo buildLogLevel := o.LogLevel switch { case len(webhook) > 0: if len(args) > 0 || len(buildName) > 0 || len(fromFile) > 0 || len(fromDir) > 0 || len(fromRepo) > 0 { return kcmdutil.UsageError(cmd, "The '--from-webhook' flag is incompatible with arguments and all '--from-*' flags") } return nil case len(args) != 1 && len(buildName) == 0: return kcmdutil.UsageError(cmd, "Must pass a name of a build config or specify build name with '--from-build' flag") } o.AsBinary = len(fromFile) > 0 || len(fromDir) > 0 || len(fromRepo) > 0 namespace, _, err := f.DefaultNamespace() if err != nil { return err } client, _, err := f.Clients() if err != nil { return err } o.Client = client var ( name = buildName resource = buildapi.Resource("builds") ) if len(name) == 0 && len(args) > 0 && len(args[0]) > 0 { mapper, _ := f.Object(false) resource, name, err = cmdutil.ResolveResource(buildapi.Resource("buildconfigs"), args[0], mapper) if err != nil { return err } switch resource { case buildapi.Resource("buildconfigs"): // no special handling required case buildapi.Resource("builds"): if len(o.ListWebhooks) == 0 { return fmt.Errorf("use --from-build to rerun your builds") } default: return fmt.Errorf("invalid resource provided: %v", resource) } } // when listing webhooks, allow --from-build to lookup a build config if resource == buildapi.Resource("builds") && len(o.ListWebhooks) > 0 { build, err := client.Builds(namespace).Get(name) if err != nil { return err } ref := build.Status.Config if ref == nil { return fmt.Errorf("the provided Build %q was not created from a BuildConfig and cannot have webhooks", name) } if len(ref.Namespace) > 0 { namespace = ref.Namespace } name = ref.Name } if len(name) == 0 { return fmt.Errorf("a resource name is required either as an argument or by using --from-build") } o.Namespace = namespace o.Name = name env, _, err := cmdutil.ParseEnv(o.Env, in) if err != nil { return err } if len(buildLogLevel) > 0 { env = append(env, kapi.EnvVar{Name: "BUILD_LOGLEVEL", Value: buildLogLevel}) } o.EnvVar = env return nil }
func TestSimpleImageChangeBuildTriggerFromImageStreamTagCustomWithConfigChange(t *testing.T) { defer testutil.DumpEtcdOnFailure(t) projectAdminClient, _ := setup(t) clusterAdminClient, err := testutil.GetClusterAdminClient(testutil.GetBaseDir() + "/openshift.local.config/master/admin.kubeconfig") if err != nil { t.Fatalf("unexpected error: %v", err) } clusterRoleBindingAccessor := policy.NewClusterRoleBindingAccessor(clusterAdminClient) subjects := []kapi.ObjectReference{ { Kind: authorizationapi.SystemGroupKind, Name: bootstrappolicy.AuthenticatedGroup, }, } options := policy.RoleModificationOptions{ RoleNamespace: testutil.Namespace(), RoleName: bootstrappolicy.BuildStrategyCustomRoleName, RoleBindingAccessor: clusterRoleBindingAccessor, Subjects: subjects, } options.AddRole() if err := testutil.WaitForPolicyUpdate(projectAdminClient, testutil.Namespace(), "create", buildapi.Resource(authorizationapi.CustomBuildResource), true); err != nil { t.Fatal(err) } imageStream := mockImageStream2(tag) imageStreamMapping := mockImageStreamMapping(imageStream.Name, "someimage", tag, "registry:8080/openshift/test-image-trigger:"+tag) strategy := customStrategy("ImageStreamTag", streamName+":"+tag) config := imageChangeBuildConfigWithConfigChange("custom-imagestreamtag", strategy) runTest(t, "SimpleImageChangeBuildTriggerFromImageStreamTagCustom", projectAdminClient, imageStream, imageStreamMapping, config, tag) }
func (a *jenkinsBootstrapper) Admit(attributes admission.Attributes) error { if a.jenkinsConfig.AutoProvisionEnabled != nil && !*a.jenkinsConfig.AutoProvisionEnabled { return nil } if len(attributes.GetSubresource()) != 0 { return nil } if attributes.GetResource().GroupResource() != buildapi.Resource("buildconfigs") && attributes.GetResource().GroupResource() != buildapi.Resource("builds") { return nil } if !needsJenkinsTemplate(attributes.GetObject()) { return nil } namespace := attributes.GetNamespace() svcName := a.jenkinsConfig.ServiceName if len(svcName) == 0 { return nil } // TODO pull this from a cache. if _, err := a.serviceClient.Services(namespace).Get(svcName); !kapierrors.IsNotFound(err) { // if it isn't a "not found" error, return the error. Either its nil and there's nothing to do or something went really wrong return err } glog.V(3).Infof("Adding new jenkins service %q to the project %q", svcName, namespace) jenkinsTemplate := jenkinscontroller.NewPipelineTemplate(namespace, a.jenkinsConfig, a.openshiftClient) objects, errs := jenkinsTemplate.Process() if len(errs) > 0 { return kutilerrors.NewAggregate(errs) } if !jenkinsTemplate.HasJenkinsService(objects) { return fmt.Errorf("template %s/%s does not contain required service %q", a.jenkinsConfig.TemplateNamespace, a.jenkinsConfig.TemplateName, a.jenkinsConfig.ServiceName) } impersonatingConfig := a.privilegedRESTClientConfig oldWrapTransport := impersonatingConfig.WrapTransport impersonatingConfig.WrapTransport = func(rt http.RoundTripper) http.RoundTripper { return authenticationclient.NewImpersonatingRoundTripper(attributes.GetUserInfo(), oldWrapTransport(rt)) } var bulkErr error bulk := &cmd.Bulk{ Mapper: &resource.Mapper{ RESTMapper: registered.RESTMapper(), ObjectTyper: kapi.Scheme, ClientMapper: resource.ClientMapperFunc(func(mapping *meta.RESTMapping) (resource.RESTClient, error) { if latest.OriginKind(mapping.GroupVersionKind) { return client.New(&impersonatingConfig) } return kclient.New(&impersonatingConfig) }), }, Op: cmd.Create, After: func(info *resource.Info, err error) bool { if kapierrors.IsAlreadyExists(err) { return false } if err != nil { bulkErr = err return true } return false }, } // we're intercepting the error we care about using After bulk.Run(objects, namespace) if bulkErr != nil { return bulkErr } glog.V(1).Infof("Jenkins Pipeline service %q created", svcName) return nil }
// RunCancelBuild implements all the necessary functionality for CancelBuild. func (o *CancelBuildOptions) RunCancelBuild() error { var builds []*buildapi.Build for _, name := range o.BuildNames { build, err := o.BuildClient.Get(name) if err != nil { o.ReportError(fmt.Errorf("build %s/%s not found", o.Namespace, name)) continue } stateMatch := false for _, state := range o.States { if strings.ToLower(string(build.Status.Phase)) == state { stateMatch = true break } } if stateMatch && !buildutil.IsBuildComplete(build) { builds = append(builds, build) } } if o.DumpLogs { for _, b := range builds { // Do not attempt to get logs from build that was not scheduled. if b.Status.Phase == buildapi.BuildPhaseNew { continue } opts := buildapi.BuildLogOptions{NoWait: true} response, err := o.Client.BuildLogs(o.Namespace).Get(b.Name, opts).Do().Raw() if err != nil { o.ReportError(fmt.Errorf("unable to fetch logs for %s/%s: %v", b.Namespace, b.Name, err)) continue } fmt.Fprintf(o.Out, "==== Build %s/%s logs ====\n", b.Namespace, b.Name) fmt.Fprint(o.Out, string(response)) } } var wg sync.WaitGroup for _, b := range builds { wg.Add(1) go func(build *buildapi.Build) { defer wg.Done() err := wait.Poll(500*time.Millisecond, 30*time.Second, func() (bool, error) { build.Status.Cancelled = true _, err := o.BuildClient.Update(build) switch { case err == nil: return true, nil case kapierrors.IsConflict(err): build, err = o.BuildClient.Get(build.Name) return false, err } return true, err }) if err != nil { o.ReportError(fmt.Errorf("build %s/%s failed to update: %v", build.Namespace, build.Name, err)) return } // Make sure the build phase is really cancelled. err = wait.Poll(500*time.Millisecond, 30*time.Second, func() (bool, error) { updatedBuild, err := o.BuildClient.Get(build.Name) if err != nil { return true, err } return updatedBuild.Status.Phase == buildapi.BuildPhaseCancelled, nil }) if err != nil { o.ReportError(fmt.Errorf("build %s/%s failed to cancel: %v", build.Namespace, build.Name, err)) return } resource, name, _ := cmdutil.ResolveResource(buildapi.Resource("builds"), build.Name, o.Mapper) kcmdutil.PrintSuccess(o.Mapper, false, o.Out, resource.Resource, name, "cancelled") }(b) } wg.Wait() if o.Restart { for _, b := range builds { request := &buildapi.BuildRequest{ObjectMeta: kapi.ObjectMeta{Name: b.Name}} build, err := o.BuildClient.Clone(request) if err != nil { o.ReportError(fmt.Errorf("build %s/%s failed to restart: %v", b.Namespace, b.Name, err)) continue } resource, name, _ := cmdutil.ResolveResource(buildapi.Resource("builds"), build.Name, o.Mapper) kcmdutil.PrintSuccess(o.Mapper, false, o.Out, resource.Resource, name, fmt.Sprintf("restarted build %q", b.Name)) } } if o.HasError { return errors.New("failure during the build cancellation") } return nil }
func (o *StartBuildOptions) Complete(f *clientcmd.Factory, in io.Reader, out io.Writer, cmd *cobra.Command, cmdFullName string, args []string) error { o.In = in o.Out = out o.ErrOut = cmd.OutOrStderr() o.Git = git.NewRepository() o.ClientConfig = f.OpenShiftClientConfig o.Mapper, _ = f.Object(false) webhook := o.FromWebhook buildName := o.FromBuild fromFile := o.FromFile fromDir := o.FromDir fromRepo := o.FromRepo buildLogLevel := o.LogLevel outputFormat := kcmdutil.GetFlagString(cmd, "output") if outputFormat != "name" && outputFormat != "" { return kcmdutil.UsageError(cmd, "Unsupported output format: %s", outputFormat) } o.ShortOutput = outputFormat == "name" switch { case len(webhook) > 0: if len(args) > 0 || len(buildName) > 0 || len(fromFile) > 0 || len(fromDir) > 0 || len(fromRepo) > 0 { return kcmdutil.UsageError(cmd, "The '--from-webhook' flag is incompatible with arguments and all '--from-*' flags") } return nil case len(args) != 1 && len(buildName) == 0: return kcmdutil.UsageError(cmd, "Must pass a name of a build config or specify build name with '--from-build' flag.\nUse \"%s get bc\" to list all available build configs.", cmdFullName) } if len(buildName) != 0 && (len(fromFile) != 0 || len(fromDir) != 0 || len(fromRepo) != 0) { // TODO: we should support this, it should be possible to clone a build to run again with new uploaded artifacts. // Doing so requires introducing a new clonebinary endpoint. return kcmdutil.UsageError(cmd, "Cannot use '--from-build' flag with binary builds") } o.AsBinary = len(fromFile) > 0 || len(fromDir) > 0 || len(fromRepo) > 0 namespace, _, err := f.DefaultNamespace() if err != nil { return err } client, _, err := f.Clients() if err != nil { return err } o.Client = client var ( name = buildName resource = buildapi.Resource("builds") ) if len(name) == 0 && len(args) > 0 && len(args[0]) > 0 { mapper, _ := f.Object(false) resource, name, err = cmdutil.ResolveResource(buildapi.Resource("buildconfigs"), args[0], mapper) if err != nil { return err } switch resource { case buildapi.Resource("buildconfigs"): // no special handling required case buildapi.Resource("builds"): if len(o.ListWebhooks) == 0 { return fmt.Errorf("use --from-build to rerun your builds") } default: return fmt.Errorf("invalid resource provided: %v", resource) } } // when listing webhooks, allow --from-build to lookup a build config if resource == buildapi.Resource("builds") && len(o.ListWebhooks) > 0 { build, err := client.Builds(namespace).Get(name) if err != nil { return err } ref := build.Status.Config if ref == nil { return fmt.Errorf("the provided Build %q was not created from a BuildConfig and cannot have webhooks", name) } if len(ref.Namespace) > 0 { namespace = ref.Namespace } name = ref.Name } if len(name) == 0 { return fmt.Errorf("a resource name is required either as an argument or by using --from-build") } o.Namespace = namespace o.Name = name env, _, err := cmdutil.ParseEnv(o.Env, in) if err != nil { return err } if len(buildLogLevel) > 0 { env = append(env, kapi.EnvVar{Name: "BUILD_LOGLEVEL", Value: buildLogLevel}) } o.EnvVar = env return nil }
func TestStop(t *testing.T) { notFound := func() runtime.Object { return &(kerrors.NewNotFound(buildapi.Resource("BuildConfig"), configName).(*kerrors.StatusError).ErrStatus) } tests := map[string]struct { oc *testclient.Fake expected []ktestclient.Action err bool }{ "simple stop": { oc: newBuildListFake(makeBuildConfig(0, false)), expected: []ktestclient.Action{ ktestclient.NewGetAction("buildconfigs", "default", configName), ktestclient.NewUpdateAction("buildconfigs", "default", makeBuildConfig(0, true)), ktestclient.NewListAction("builds", "default", kapi.ListOptions{LabelSelector: buildutil.BuildConfigSelector(configName)}), ktestclient.NewListAction("builds", "default", kapi.ListOptions{LabelSelector: buildutil.BuildConfigSelectorDeprecated(configName)}), ktestclient.NewDeleteAction("buildconfigs", "default", configName), }, err: false, }, "multiple builds": { oc: newBuildListFake(makeBuildConfig(4, false), makeBuildList(4)), expected: []ktestclient.Action{ ktestclient.NewGetAction("buildconfigs", "default", configName), ktestclient.NewUpdateAction("buildconfigs", "default", makeBuildConfig(4, true)), ktestclient.NewListAction("builds", "default", kapi.ListOptions{LabelSelector: buildutil.BuildConfigSelector(configName)}), ktestclient.NewDeleteAction("builds", "default", "build-1"), ktestclient.NewDeleteAction("builds", "default", "build-3"), ktestclient.NewListAction("builds", "default", kapi.ListOptions{LabelSelector: buildutil.BuildConfigSelectorDeprecated(configName)}), ktestclient.NewDeleteAction("builds", "default", "build-2"), ktestclient.NewDeleteAction("builds", "default", "build-4"), ktestclient.NewDeleteAction("buildconfigs", "default", configName), }, err: false, }, "no config, some builds": { oc: newBuildListFake(makeBuildList(2)), expected: []ktestclient.Action{ ktestclient.NewGetAction("buildconfigs", "default", configName), ktestclient.NewListAction("builds", "default", kapi.ListOptions{LabelSelector: buildutil.BuildConfigSelector(configName)}), ktestclient.NewDeleteAction("builds", "default", "build-1"), ktestclient.NewListAction("builds", "default", kapi.ListOptions{LabelSelector: buildutil.BuildConfigSelectorDeprecated(configName)}), ktestclient.NewDeleteAction("builds", "default", "build-2"), }, err: false, }, "no config, no builds": { oc: testclient.NewSimpleFake(notFound()), expected: []ktestclient.Action{ ktestclient.NewGetAction("buildconfigs", "default", configName), ktestclient.NewListAction("builds", "default", kapi.ListOptions{LabelSelector: buildutil.BuildConfigSelector(configName)}), ktestclient.NewListAction("builds", "default", kapi.ListOptions{LabelSelector: buildutil.BuildConfigSelectorDeprecated(configName)}), }, err: true, }, "config, no builds": { oc: testclient.NewSimpleFake(makeBuildConfig(0, false)), expected: []ktestclient.Action{ ktestclient.NewGetAction("buildconfigs", "default", configName), ktestclient.NewUpdateAction("buildconfigs", "default", makeBuildConfig(0, true)), ktestclient.NewListAction("builds", "default", kapi.ListOptions{LabelSelector: buildutil.BuildConfigSelector(configName)}), ktestclient.NewListAction("builds", "default", kapi.ListOptions{LabelSelector: buildutil.BuildConfigSelectorDeprecated(configName)}), ktestclient.NewDeleteAction("buildconfigs", "default", configName), }, err: false, }, } for testName, test := range tests { reaper := &BuildConfigReaper{oc: test.oc, pollInterval: time.Millisecond, timeout: time.Millisecond} err := reaper.Stop("default", configName, 1*time.Second, nil) if !test.err && err != nil { t.Errorf("%s: unexpected error: %v", testName, err) } if test.err && err == nil { t.Errorf("%s: expected an error", testName) } if len(test.oc.Actions()) != len(test.expected) { t.Fatalf("%s: unexpected actions: %v, expected %v", testName, test.oc.Actions(), test.expected) } for j, actualAction := range test.oc.Actions() { if !actionsAreEqual(actualAction, test.expected[j]) { t.Errorf("%s: unexpected action: %v, expected %v", testName, actualAction, test.expected[j]) } } } }