Skip to content
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

adds openshift project key preflight check #630

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,11 @@ dependencies {
implementation 'org.springframework.security:spring-security-jwt:1.1.0.RELEASE'
implementation 'org.springframework.security.oauth:spring-security-oauth2:2.4.0.RELEASE'

implementation(group: 'com.openshift', name: 'openshift-restclient-java', version: '9.0.0.Final') {
exclude(group: 'org.slf4j', module: 'slf4j-api:')
exclude(group: 'org.slf4j', module: 'slf4j-log4j12')
}

// azure ad
implementation('com.microsoft.azure:azure-active-directory-spring-boot-starter:2.2.2')

Expand Down
15 changes: 15 additions & 0 deletions docs/modules/provisioning-app/pages/configuration.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,21 @@ jira.admin_user=jira_admin
Note: if the pair of properties is not defined for a third party tool, the logged in user's credentials are used to authenticate against the application.
The credentials are read by caling the method _getUserName_ and _getUserPassword_ from https://github.com/opendevstack/ods-provisioning-app/blob/master/src/main/java/org/opendevstack/provision/adapter/IODSAuthnzAdapter[IODSAuthnzAdapter]]. See also implementation of _org.opendevstack.provision.services.BaseServiceAdapter#authenticatedCall()_

=== Other configuration

To adapt the provisioning app to your infrastructure following properties will help you to disable some adapters/services.

To disable the confluence adapter you can add this property to the application properties:
```
adapters.confluence.enabled=false
```

The Openshift Service currently is used to verify that a project key does not exists in the cluster before provisioning a project.
If you need to disable it, you can add this property to the application properties:
```
services.openshift.enabled=false
```

== FAQ

. Where is the provision app deployed? +
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/*
* Copyright 2017-2019 the original author or 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 org.opendevstack.provision.config;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import org.opendevstack.provision.services.openshift.OpenshiftClient;
import org.opendevstack.provision.services.openshift.OpenshiftService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.Assert;

/** @author Sebastian Titakis */
@Configuration
@ConditionalOnProperty(
name = "services.openshift.enabled",
havingValue = "true",
matchIfMissing = true)
public class OpenshiftServiceConfig {

private static final Logger logger = LoggerFactory.getLogger(OpenshiftServiceConfig.class);

public static final String DEFAULT_KUBERNETES_IO_SERVICEACCOUNT_TOKEN_FILE =
"file:///var/run/secrets/kubernetes.io/serviceaccount/token";

@Value(
"${openshift.provisioning-app.service-account.file:"
+ DEFAULT_KUBERNETES_IO_SERVICEACCOUNT_TOKEN_FILE
+ "}")
private String ocTokenResourceFile;

@Value("${openshift.provisioning-app.service-account.token:}")
private String ocToken;

@Value("${openshift.api.uri}")
private String openshiftApiUri;

@Autowired private ResourceLoader resourceLoader;

@Bean
public OpenshiftService openshiftService(OpenshiftClient openshiftClient) {
return new OpenshiftService(openshiftClient);
}

@Bean
public OpenshiftClient openshiftClient() {

if (null != ocToken && !ocToken.isEmpty()) {
logger.info(
"Found oc token configured in property 'openshift.provisioning-app.service-account.token'. Using it to log into to openshift! [openshift.api.uri={}]",
openshiftApiUri);

return create(openshiftApiUri, ocToken);

} else if (null != ocTokenResourceFile) {

if (!ocTokenResourceFile.equals(DEFAULT_KUBERNETES_IO_SERVICEACCOUNT_TOKEN_FILE)) {
logger.info(
"Found oc service account file configured in property 'openshift.provisioning-app.service-account.file'. Using it to log into to openshift! [openshift.api.uri={}, serviceAccountTokenFile={}]",
openshiftApiUri,
ocTokenResourceFile);
} else {
logger.info(
"Using default service account file to connect to openshift! [openshift.api.uri={}, serviceAccountTokenFile={}]",
openshiftApiUri,
DEFAULT_KUBERNETES_IO_SERVICEACCOUNT_TOKEN_FILE);
}

Resource resource = resourceLoader.getResource(ocTokenResourceFile);

if (!resource.exists()) {
throw new RuntimeException(
"Cannot load oc token from file because file does not exists! [file="
+ ocTokenResourceFile
+ "]");
}

return create(openshiftApiUri, resource);

} else {
throw new RuntimeException("This should never happens! Ask developers to take a look!");
}
}

private OpenshiftClient create(String openshiftApiUri, Resource token) {
Assert.notNull(token, "Parameter 'token' is null!");

try {
if (!token.exists()) {
throw new RuntimeException(
String.format(
"File with oc token does not exists! [file=%s]!", token.getURI().toString()));
}

String ocToken = new String(Files.readAllBytes(Path.of(token.getURI())));

return create(openshiftApiUri, ocToken);

} catch (IOException ex) {
logger.error("Failed to create openshift service!", ex);
throw new RuntimeException(ex);
}
}

private OpenshiftClient create(String openshiftApiUri, String token) {
Assert.notNull(openshiftApiUri, "Parameter 'openshiftApiUri' is null!");
Assert.notNull(token, "Parameter 'token' is null!");

return new OpenshiftClient(openshiftApiUri, token);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import org.opendevstack.provision.model.jenkins.Job;
import org.opendevstack.provision.services.MailAdapter;
import org.opendevstack.provision.services.StorageAdapter;
import org.opendevstack.provision.services.openshift.OpenshiftService;
import org.opendevstack.provision.storage.IStorage;
import org.opendevstack.provision.util.exception.ProjectAlreadyExistsException;
import org.slf4j.Logger;
Expand Down Expand Up @@ -87,6 +88,9 @@ public class ProjectApiController {
@Autowired(required = false)
private ICollaborationAdapter confluenceAdapter;

@Autowired(required = false)
private OpenshiftService openshiftService;

@Autowired private ISCMAdapter bitbucketAdapter;
@Autowired private IJobExecutionAdapter jenkinsPipelineAdapter;
@Autowired private MailAdapter mailAdapter;
Expand Down Expand Up @@ -117,6 +121,9 @@ public class ProjectApiController {
@Value("${adapters.confluence.enabled:true}")
private boolean confluenceAdapterEnable;

@Value("${services.openshift.enabled:true}")
private boolean openshiftServiceEnable;
stitakis marked this conversation as resolved.
Show resolved Hide resolved

@PostConstruct
public void postConstruct() {
logger.info(
Expand Down Expand Up @@ -338,6 +345,10 @@ private List<CheckPreconditionFailure> checkPreconditions(OpenProjectData newPro

if (newProject.platformRuntime) {
results.addAll(bitbucketAdapter.checkCreateProjectPreconditions(newProject));

if (isOpenshiftServiceEnable() && null != openshiftService) {
results.addAll(openshiftService.checkCreateProjectPreconditions(newProject));
}
}

if (results.isEmpty()) {
Expand Down Expand Up @@ -958,6 +969,14 @@ public IStorage getDirectStorage() {
return directStorage;
}

public boolean isOpenshiftServiceEnable() {
return openshiftServiceEnable;
}

public void setOpenshiftServiceEnable(boolean openshiftServiceEnable) {
this.openshiftServiceEnable = openshiftServiceEnable;
}

public boolean isConfluenceAdapterEnable() {
return confluenceAdapterEnable;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright 2017-2019 the original author or 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 org.opendevstack.provision.services.openshift;

import com.openshift.restclient.ClientBuilder;
import com.openshift.restclient.IClient;
import com.openshift.restclient.model.IResource;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/** @author Sebastian Titakis */
public class OpenshiftClient {

private final IClient iClient;

private final String url;

public OpenshiftClient(String url, String token) {
this(url, new ClientBuilder(url).usingToken(token).build());
}

public OpenshiftClient(String url, IClient ocClient) {
this.url = url;
this.iClient = ocClient;
}

public Set<String> projects() {

List<IResource> projectResource = iClient.list("Project");

return projectResource.stream().map(project -> project.getName()).collect(Collectors.toSet());
}

public String getUrl() {
return url;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* Copyright 2017-2019 the original author or authors.
stitakis marked this conversation as resolved.
Show resolved Hide resolved
*
* 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 org.opendevstack.provision.services.openshift;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.opendevstack.provision.adapter.exception.AdapterException;
import org.opendevstack.provision.adapter.exception.CreateProjectPreconditionException;
import org.opendevstack.provision.controller.CheckPreconditionFailure;
import org.opendevstack.provision.model.OpenProjectData;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;

/** @author Sebastian Titakis */
stitakis marked this conversation as resolved.
Show resolved Hide resolved
public class OpenshiftService {

private static final Logger logger = LoggerFactory.getLogger(OpenshiftService.class);

public static final String SERVICE_NAME = "openshiftService";

private OpenshiftClient openshiftClient;

public OpenshiftService(OpenshiftClient openshiftClient) {
this.openshiftClient = openshiftClient;
}

public List<CheckPreconditionFailure> checkCreateProjectPreconditions(OpenProjectData newProject)
throws CreateProjectPreconditionException {

try {
Assert.notNull(newProject, "Parameter 'newProject' is null!");
Assert.notNull(
newProject.projectKey, "Properties 'projectKey' of parameter 'newProject' is null!");

logger.info("checking create project preconditions for project '{}'!", newProject.projectKey);

List<CheckPreconditionFailure> preconditionFailures =
createProjectKeyExistsCheck(newProject.projectKey);

logger.info(
"done with check create project preconditions for project '{}'!", newProject.projectKey);

return preconditionFailures;

} catch (AdapterException e) {
throw new CreateProjectPreconditionException(SERVICE_NAME, newProject.projectKey, e);
} catch (Exception e) {
String message =
String.format(
"Unexpected error when checking precondition for creation of project '%s'",
newProject.projectKey);
logger.error(message, e);
throw new CreateProjectPreconditionException(SERVICE_NAME, newProject.projectKey, message);
}
}

public List<CheckPreconditionFailure> createProjectKeyExistsCheck(String projectKey) {

try {
logger.info("Checking if ODS project '{}-*' exists in openshift!", projectKey);

List<CheckPreconditionFailure> preconditionFailures = new ArrayList<>();

Set<String> projects = openshiftClient.projects();
List<String> existingProjects =
projects.stream()
.filter(s -> s.toLowerCase().startsWith(projectKey.toLowerCase() + "-"))
.collect(Collectors.toList());

if (existingProjects.size() > 0) {
String message =
String.format(
"Project name (namespace) with prefix '%s' already exists in '%s'! [existingProjects=%s]",
projectKey, SERVICE_NAME, Arrays.asList(existingProjects.toArray()));
preconditionFailures.add(CheckPreconditionFailure.getProjectExistsInstance(message));
}

return preconditionFailures;

} catch (Exception ex) {
throw new AdapterException(ex);
}
}
}
2 changes: 2 additions & 0 deletions src/main/resources/application-odsbox.properties
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ bitbucket.opendevstack.project=opendevstack

# openshift properties
openshift.apps.basedomain=.ocp.odsbox.lan
openshift.provisioning-app.service-account-file-path=file:///var/run/secrets/kubernetes.io/serviceaccount/token
openshift.api.uri=https://api.odsbox.lan:8443
openshift.console.uri=https://ocp.odsbox.lan:8443/console/project/

# webhook proxy used to run create project jenkinsfile
Expand Down
2 changes: 2 additions & 0 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ ods.git-ref=master
openshift.project.upgrade=false
openshift.apps.basedomain=.192.168.56.101.nip.io
openshift.console.uri=https://192.168.56.101:8443/console
openshift.api.uri=https://192.168.56.101:8443

#openshift project name patterns
openshift.test.project.name.pattern=%s/project/%s-test
openshift.dev.project.name.pattern=%s/project/%s-dev
Expand Down
Loading