Skip to content

Commit

Permalink
feat(cf): "Run Job" stage support for CloudFoundry (#3789)
Browse files Browse the repository at this point in the history
  • Loading branch information
Pierre Delagrave authored and Jammy Louie committed Jun 21, 2019
1 parent abc102e commit a8e26cc
Show file tree
Hide file tree
Showing 13 changed files with 481 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,6 @@ public interface CloudFoundryClient {
ServiceInstances getServiceInstances();

ServiceKeys getServiceKeys();

Tasks getTasks();
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,16 @@
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.netflix.spinnaker.clouddriver.cloudfoundry.client.api.*;
import com.netflix.spinnaker.clouddriver.cloudfoundry.client.api.ApplicationService;
import com.netflix.spinnaker.clouddriver.cloudfoundry.client.api.AuthenticationService;
import com.netflix.spinnaker.clouddriver.cloudfoundry.client.api.ConfigService;
import com.netflix.spinnaker.clouddriver.cloudfoundry.client.api.DomainService;
import com.netflix.spinnaker.clouddriver.cloudfoundry.client.api.OrganizationService;
import com.netflix.spinnaker.clouddriver.cloudfoundry.client.api.RouteService;
import com.netflix.spinnaker.clouddriver.cloudfoundry.client.api.ServiceInstanceService;
import com.netflix.spinnaker.clouddriver.cloudfoundry.client.api.ServiceKeyService;
import com.netflix.spinnaker.clouddriver.cloudfoundry.client.api.SpaceService;
import com.netflix.spinnaker.clouddriver.cloudfoundry.client.api.TaskService;
import com.netflix.spinnaker.clouddriver.cloudfoundry.client.model.Token;
import com.squareup.okhttp.Interceptor;
import com.squareup.okhttp.OkHttpClient;
Expand Down Expand Up @@ -69,6 +78,7 @@ public class HttpCloudFoundryClient implements CloudFoundryClient {
private Applications applications;
private ServiceInstances serviceInstances;
private ServiceKeys serviceKeys;
private Tasks tasks;

private final RequestInterceptor oauthInterceptor =
new RequestInterceptor() {
Expand Down Expand Up @@ -192,6 +202,7 @@ public HttpCloudFoundryClient(
this.routes =
new Routes(account, createService(RouteService.class), applications, domains, spaces);
this.serviceKeys = new ServiceKeys(createService(ServiceKeyService.class), spaces);
this.tasks = new Tasks(createService(TaskService.class));
}

private static OkHttpClient createHttpClient(boolean skipSslValidation) {
Expand Down Expand Up @@ -293,4 +304,9 @@ public ServiceInstances getServiceInstances() {
public ServiceKeys getServiceKeys() {
return serviceKeys;
}

@Override
public Tasks getTasks() {
return tasks;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright 2019 Pivotal, Inc.
*
* 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 com.netflix.spinnaker.clouddriver.cloudfoundry.client;

import static com.netflix.spinnaker.clouddriver.cloudfoundry.client.CloudFoundryClientUtils.safelyCall;

import com.netflix.spinnaker.clouddriver.cloudfoundry.client.api.TaskService;
import com.netflix.spinnaker.clouddriver.cloudfoundry.client.model.v3.CreateTask;
import com.netflix.spinnaker.clouddriver.cloudfoundry.client.model.v3.Task;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
public class Tasks {
private final TaskService api;

public Task createTask(String applicationGuid, String command, String name) {
CreateTask createTask = new CreateTask(name, command);

return safelyCall(() -> api.createTask(applicationGuid, createTask))
.orElseThrow(ResourceNotFoundException::new);
}

public Task getTask(String id) {
return safelyCall(() -> api.getTask(id)).orElseThrow(ResourceNotFoundException::new);
}

public Task cancelTask(String id) {
return safelyCall(() -> api.cancelTask(id, "")).orElseThrow(ResourceNotFoundException::new);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright 2019 Pivotal, Inc.
*
* 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 com.netflix.spinnaker.clouddriver.cloudfoundry.client.api;

import com.netflix.spinnaker.clouddriver.cloudfoundry.client.model.v3.CreateTask;
import com.netflix.spinnaker.clouddriver.cloudfoundry.client.model.v3.Task;
import retrofit.http.Body;
import retrofit.http.GET;
import retrofit.http.POST;
import retrofit.http.Path;

public interface TaskService {

@POST("/v3/apps/{guid}/tasks")
Task createTask(@Path("guid") String guid, @Body CreateTask body);

@GET("/v3/tasks/{guid}")
Task getTask(@Path("guid") String guid);

@POST("/v3/tasks/{guid}/actions/cancel")
Task cancelTask(@Path("guid") String guid, @Body Object emptyBody);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright 2019 Pivotal, Inc.
*
* 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 com.netflix.spinnaker.clouddriver.cloudfoundry.client.model.v3;

import javax.annotation.Nullable;
import lombok.Value;

@Value
public class CreateTask {
@Nullable private final String name;
private final String command;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright 2019 Pivotal, Inc.
*
* 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 com.netflix.spinnaker.clouddriver.cloudfoundry.client.model.v3;

import java.time.ZonedDateTime;
import lombok.Value;

@Value
public class Task {
private final String guid;
private final String name;
private final State state;
private final ZonedDateTime createdAt;
private final ZonedDateTime updatedAt;

public enum State {
SUCCEEDED,
RUNNING,
FAILED
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,10 @@
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.SafeConstructor;

abstract class AbstractCloudFoundryAtomicOperationConverter
public abstract class AbstractCloudFoundryAtomicOperationConverter
extends AbstractAtomicOperationsCredentialsSupport {
Optional<CloudFoundrySpace> findSpace(String region, CloudFoundryClient client) {

protected Optional<CloudFoundrySpace> findSpace(String region, CloudFoundryClient client) {
return client.getOrganizations().findSpaceByRegion(region);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright 2019 Pivotal, Inc.
*
* 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 com.netflix.spinnaker.clouddriver.cloudfoundry.job;

import com.netflix.spinnaker.clouddriver.cloudfoundry.CloudFoundryCloudProvider;
import com.netflix.spinnaker.clouddriver.cloudfoundry.client.model.v3.Task;
import com.netflix.spinnaker.clouddriver.cloudfoundry.security.CloudFoundryCredentials;
import com.netflix.spinnaker.clouddriver.model.JobProvider;
import com.netflix.spinnaker.clouddriver.security.AccountCredentials;
import com.netflix.spinnaker.clouddriver.security.AccountCredentialsProvider;
import java.util.Map;
import lombok.Getter;
import org.apache.commons.lang3.NotImplementedException;
import org.springframework.stereotype.Component;

@Component
public class CloudFoundryJobProvider implements JobProvider<CloudFoundryJobStatus> {

@Getter private String platform = CloudFoundryCloudProvider.ID;
private final AccountCredentialsProvider accountCredentialsProvider;

public CloudFoundryJobProvider(AccountCredentialsProvider accountCredentialsProvider) {
this.accountCredentialsProvider = accountCredentialsProvider;
}

@Override
public CloudFoundryJobStatus collectJob(String account, String location, String id) {
AccountCredentials credentials = accountCredentialsProvider.getCredentials(account);
if (!(credentials instanceof CloudFoundryCredentials)) {
return null;
}

Task task = ((CloudFoundryCredentials) credentials).getClient().getTasks().getTask(id);
return CloudFoundryJobStatus.fromTask(task, account, location);
}

@Override
public Map<String, Object> getFileContents(
String account, String location, String id, String fileName) {
throw new NotImplementedException("");
}

@Override
public void cancelJob(String account, String location, String taskGuid) {
AccountCredentials credentials = accountCredentialsProvider.getCredentials(account);
if (!(credentials instanceof CloudFoundryCredentials)) {
return;
}

((CloudFoundryCredentials) credentials).getClient().getTasks().cancelTask(taskGuid);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* Copyright 2019 Pivotal, Inc.
*
* 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 com.netflix.spinnaker.clouddriver.cloudfoundry.job;

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.netflix.spinnaker.clouddriver.cloudfoundry.CloudFoundryCloudProvider;
import com.netflix.spinnaker.clouddriver.cloudfoundry.client.model.v3.Task;
import com.netflix.spinnaker.clouddriver.cloudfoundry.client.model.v3.Task.State;
import com.netflix.spinnaker.clouddriver.model.JobState;
import com.netflix.spinnaker.clouddriver.model.JobStatus;
import java.io.Serializable;
import java.util.Collections;
import java.util.Map;
import javax.annotation.Nullable;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;

@Data
@EqualsAndHashCode(callSuper = false)
@Builder
@JsonDeserialize(builder = CloudFoundryJobStatus.CloudFoundryJobStatusBuilder.class)
public class CloudFoundryJobStatus implements JobStatus {
@Nullable private String name;

private String account;

private String id;

private String location;

private final String provider = CloudFoundryCloudProvider.ID;

private JobState jobState;

private Long createdTime;

@Nullable private Long completedTime;

@Override
public Map<String, ? extends Serializable> getCompletionDetails() {
return Collections.emptyMap();
}

public static CloudFoundryJobStatus fromTask(Task task, String account, String location) {
State state = task.getState();
CloudFoundryJobStatusBuilder builder = CloudFoundryJobStatus.builder();
switch (state) {
case FAILED:
builder.jobState(JobState.Failed);
builder.completedTime(task.getUpdatedAt().toInstant().toEpochMilli());
break;
case RUNNING:
builder.jobState(JobState.Running);
break;
case SUCCEEDED:
builder.jobState(JobState.Succeeded);
builder.completedTime(task.getUpdatedAt().toInstant().toEpochMilli());
break;
default:
builder.jobState(JobState.Unknown);
}
return builder
.name(task.getName())
.account(account)
.id(task.getGuid())
.location(location)
.createdTime(task.getCreatedAt().toInstant().toEpochMilli())
.build();
}
}
Loading

0 comments on commit a8e26cc

Please sign in to comment.