Skip to content

Commit

Permalink
Add a /refresh endpoint for forcing an update in an AppRepository (#2138
Browse files Browse the repository at this point in the history
)

* Add a new API endpoint to force a refresh in a appRepository

Rel #2062
For instance, "POST /api/v1/clusters/default/namespaces/kubeapps/apprepositories/bitnami/refresh" will trigger a refresh in the "bitnami" appRepository in the namespace "kubeapps"

* Change endpoint of appRepository refresh button

Rel #2062
Now it calls the new POST .../refresh endpoint implemented in the backend.

* Change ordering in some arguments to "cluster, namespace, other args"

Rel #2147

* Remove unnecessary namespace check

* Revert unnecessary change

Also fix minor typo

* Remove unnecessary test step

Since refreshing an apprepository no longer requires to get the repo to send an edition later, this test step (faulty) can be removed
  • Loading branch information
antgamdia committed Nov 9, 2020
1 parent 38cc3ea commit 3c5fdf4
Show file tree
Hide file tree
Showing 6 changed files with 65 additions and 22 deletions.
16 changes: 0 additions & 16 deletions dashboard/src/actions/repos.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -149,22 +149,6 @@ describe("deleteRepo", () => {
});

describe("resyncRepo", () => {
it("dispatches errorRepos if error on #get", async () => {
AppRepository.get = jest.fn().mockImplementationOnce(() => {
throw new Error("Boom!");
});

const expectedActions = [
{
type: getType(repoActions.errorRepos),
payload: { err: new Error("Boom!"), op: "update" },
},
];

await store.dispatch(repoActions.resyncRepo("foo", "my-namespace"));
expect(store.getActions()).toEqual(expectedActions);
});

it("dispatches errorRepos if error on #update", async () => {
AppRepository.resync = jest.fn().mockImplementationOnce(() => {
throw new Error("Boom!");
Expand Down
9 changes: 3 additions & 6 deletions dashboard/src/shared/AppRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,9 @@ export class AppRepository {
}

public static async resync(cluster: string, namespace: string, name: string) {
const repo = await AppRepository.get(cluster, namespace, name);
repo.spec.resyncRequests = repo.spec.resyncRequests || 0;
repo.spec.resyncRequests++;
const { data } = await axiosWithAuth.put(
AppRepository.getSelfLink(cluster, namespace, name),
repo,
const { data } = await axiosWithAuth.post(
url.backend.apprepositories.refresh(cluster, namespace, name),
null,
);
return data;
}
Expand Down
2 changes: 2 additions & 0 deletions dashboard/src/shared/url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ export const backend = {
validate: (cluster: string) => `${backend.apprepositories.base(cluster, "kubeapps")}/validate`,
delete: (cluster: string, namespace: string, name: string) =>
`${backend.apprepositories.base(cluster, namespace)}/${name}`,
refresh: (cluster: string, namespace: string, name: string) =>
`${backend.apprepositories.base(cluster, namespace)}/${name}/refresh`,
update: (cluster: string, namespace: string, name: string) =>
`${backend.apprepositories.base(cluster, namespace)}/${name}`,
},
Expand Down
33 changes: 33 additions & 0 deletions pkg/http-handler/http-handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,37 @@ func UpdateAppRepository(handler kube.AuthHandler) func(w http.ResponseWriter, r
}
}

// RefreshAppRepository forces a refresh in a given apprepository (by updating resyncRequests property)
func RefreshAppRepository(handler kube.AuthHandler) func(w http.ResponseWriter, req *http.Request) {
return func(w http.ResponseWriter, req *http.Request) {
requestNamespace, requestCluster := getNamespaceAndCluster(req)
repoName := mux.Vars(req)["name"]
token := auth.ExtractToken(req.Header.Get("Authorization"))

clientset, err := handler.AsUser(token, requestCluster)
if err != nil {
returnK8sError(err, w)
return
}

appRepo, err := clientset.RefreshAppRepository(repoName, requestNamespace)
if err != nil {
returnK8sError(err, w)
return
}
w.WriteHeader(http.StatusOK)
response := appRepositoryResponse{
AppRepository: *appRepo,
}
responseBody, err := json.Marshal(response)
if err != nil {
JSONError(w, err.Error(), http.StatusInternalServerError)
return
}
w.Write(responseBody)
}
}

// ValidateAppRepository returns a 200 if the connection to the AppRepo can be established
func ValidateAppRepository(handler kube.AuthHandler) func(w http.ResponseWriter, req *http.Request) {
return func(w http.ResponseWriter, req *http.Request) {
Expand Down Expand Up @@ -274,12 +305,14 @@ func SetupDefaultRoutes(r *mux.Router, clustersConfig kube.ClustersConfig) error
r.Methods("PUT").Path("/namespaces/{namespace}/apprepositories/{name}").Handler(http.HandlerFunc(UpdateAppRepository(backendHandler)))
r.Methods("DELETE").Path("/namespaces/{namespace}/apprepositories/{name}").Handler(http.HandlerFunc(DeleteAppRepository(backendHandler)))
r.Methods("GET").Path("/namespaces/{namespace}/operator/{name}/logo").Handler(http.HandlerFunc(GetOperatorLogo(backendHandler)))

r.Methods("GET").Path("/clusters/{cluster}/namespaces").Handler(http.HandlerFunc(GetNamespaces(backendHandler)))
r.Methods("GET").Path("/clusters/{cluster}/apprepositories").Handler(http.HandlerFunc(ListAppRepositories(backendHandler)))
r.Methods("GET").Path("/clusters/{cluster}/namespaces/{namespace}/apprepositories").Handler(http.HandlerFunc(ListAppRepositories(backendHandler)))
r.Methods("POST").Path("/clusters/{cluster}/namespaces/{namespace}/apprepositories").Handler(http.HandlerFunc(CreateAppRepository(backendHandler)))
r.Methods("POST").Path("/clusters/{cluster}/namespaces/{namespace}/apprepositories/validate").Handler(http.HandlerFunc(ValidateAppRepository(backendHandler)))
r.Methods("PUT").Path("/clusters/{cluster}/namespaces/{namespace}/apprepositories/{name}").Handler(http.HandlerFunc(UpdateAppRepository(backendHandler)))
r.Methods("POST").Path("/clusters/{cluster}/namespaces/{namespace}/apprepositories/{name}/refresh").Handler(http.HandlerFunc(RefreshAppRepository(backendHandler)))
r.Methods("DELETE").Path("/clusters/{cluster}/namespaces/{namespace}/apprepositories/{name}").Handler(http.HandlerFunc(DeleteAppRepository(backendHandler)))
r.Methods("GET").Path("/clusters/{cluster}/namespaces/{namespace}/operator/{name}/logo").Handler(http.HandlerFunc(GetOperatorLogo(backendHandler)))
return nil
Expand Down
5 changes: 5 additions & 0 deletions pkg/kube/fake.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ func (c *FakeHandler) CreateAppRepository(appRepoBody io.ReadCloser, requestName
return c.CreatedRepo, c.Err
}

// RefreshAppRepository fake
func (c *FakeHandler) RefreshAppRepository(repoName string, requestNamespace string) (*v1alpha1.AppRepository, error) {
return c.UpdatedRepo, c.Err
}

// UpdateAppRepository fake
func (c *FakeHandler) UpdateAppRepository(appRepoBody io.ReadCloser, requestNamespace string) (*v1alpha1.AppRepository, error) {
return c.UpdatedRepo, c.Err
Expand Down
22 changes: 22 additions & 0 deletions pkg/kube/kube_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ type handler interface {
ListAppRepositories(requestNamespace string) (*v1alpha1.AppRepositoryList, error)
CreateAppRepository(appRepoBody io.ReadCloser, requestNamespace string) (*v1alpha1.AppRepository, error)
UpdateAppRepository(appRepoBody io.ReadCloser, requestNamespace string) (*v1alpha1.AppRepository, error)
RefreshAppRepository(repoName string, requestNamespace string) (*v1alpha1.AppRepository, error)
DeleteAppRepository(name, namespace string) error
GetNamespaces() ([]corev1.Namespace, error)
GetSecret(name, namespace string) (*corev1.Secret, error)
Expand Down Expand Up @@ -443,6 +444,27 @@ func (a *userHandler) UpdateAppRepository(appRepoBody io.ReadCloser, requestName
return appRepo, nil
}

// RefreshAppRepository forces a refresh in a given apprepository (by updating resyncRequests property)
func (a *userHandler) RefreshAppRepository(repoName string, requestNamespace string) (*v1alpha1.AppRepository, error) {
// Retrieve the repo object with name=repoName
appRepo, err := a.clientset.KubeappsV1alpha1().AppRepositories(requestNamespace).Get(context.TODO(), repoName, metav1.GetOptions{})
if err != nil {
return nil, err
}

// An update is forced if the ResyncRequests property changes,
// so we increase it in the retrieved object
appRepo.Spec.ResyncRequests++

// Update existing repo with the new spec (ie ResyncRequests++)
appRepo, err = a.clientset.KubeappsV1alpha1().AppRepositories(requestNamespace).Update(context.TODO(), appRepo, metav1.UpdateOptions{})
if err != nil {
return nil, err
}

return appRepo, nil
}

// DeleteAppRepository deletes an AppRepository resource from a namespace.
func (a *userHandler) DeleteAppRepository(repoName, repoNamespace string) error {
appRepo, err := a.clientset.KubeappsV1alpha1().AppRepositories(repoNamespace).Get(context.TODO(), repoName, metav1.GetOptions{})
Expand Down

0 comments on commit 3c5fdf4

Please sign in to comment.